<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no"><title>五、GC 性能优化 – GC 算法(实现篇) | 云少IT</title><meta name="keywords" content="JVM"><meta name="author" content="云少"><meta name="copyright" content="云少"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="mobile-web-app-capable" content="yes"><meta name="apple-touch-fullscreen" content="yes"><meta name="apple-mobile-web-app-title" content="五、GC 性能优化 – GC 算法(实现篇)"><meta name="application-name" content="五、GC 性能优化 – GC 算法(实现篇)"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="#ffffff"><meta property="og:type" content="article"><meta property="og:title" content="五、GC 性能优化 – GC 算法(实现篇)"><meta property="og:url" content="https://it985.github.io/posts/46b81885.html"><meta property="og:site_name" content="云少IT"><meta property="og:description" content="五、GC 性能优化 – GC 算法(实现篇) master ，这是我的小站,欢迎访问哦~~  学习了GC算法的相关概念之后, 我们将介绍在JVM中这些算法的具体实现。首先要记住的是, 大多数JVM都需要使用两种不同的GC算法 —— 一种用来清理年轻代, 另一种用来清理老年代。 我们可以选择JVM内置"><meta property="og:locale" content="zh-CN"><meta property="og:image" content="https://cn.bing.com/th?id=OHR.Dargavs_EN-US4957085337_UHD.jpg"><meta property="article:author" content="云少"><meta property="article:tag" content="云少IT,IT,技术,分享,程序员,博客,教程,工具,框架,bug,java,spring,数据库,"><meta name="twitter:card" content="summary"><meta name="twitter:image" content="https://cn.bing.com/th?id=OHR.Dargavs_EN-US4957085337_UHD.jpg"><meta name="description" content="五、GC 性能优化 – GC 算法(实现篇) master ，这是我的小站,欢迎访问哦~~  学习了GC算法的相关概念之后, 我们将介绍在JVM中这些算法的具体实现。首先要记住的是, 大多数JVM都需要使用两种不同的GC算法 —— 一种用来清理年轻代, 另一种用来清理老年代。 我们可以选择JVM内置"><link rel="shortcut icon" href="/img/logo.webp"><link rel="canonical" href="https://it985.github.io/posts/46b81885"><link rel="preconnect" href="//npm.elemecdn.com"><link rel="preconnect" href="//npm.onmicrosoft.cn"><link rel="preconnect" href="//www.google-analytics.com" crossorigin=""><link rel="preconnect" href="//busuanzi.ibruce.info"><meta name="google-site-verification" content="NuBZ4r-QCqSgo4XUScdEsQW0bolIHEiVGq4A16ndPQA"><meta name="baidu-site-verification" content="code-xxx"><meta name="msvalidate.01" content="xxx"><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.cbd.int/@fortawesome/fontawesome-free@6.4.0/css/all.min.css" media="print" onload='this.media="all"'><link rel="stylesheet" href="https://cdn.cbd.int/node-snackbar@0.1.16/dist/snackbar.min.css" media="print" onload='this.media="all"'><link rel="stylesheet" href="https://cdn.cbd.int/@fancyapps/ui@5.0.20/dist/fancybox/fancybox.css" media="print" onload='this.media="all"'><link rel="stylesheet" href="https://npm.elemecdn.com/anzhiyu-theme-static@1.0.0/swiper/swiper.min.css" media="print" onload='this.media="all"'><script async src="https://www.googletagmanager.com/gtag/js?id=G-3VMKW5TZBM"></script><script>function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config","G-3VMKW5TZBM")</script><script>const GLOBAL_CONFIG={linkPageTop:{enable:!0,title:"与数百名博主无限进步",addFriendPlaceholder:"昵称（请勿包含博客等字样）：\n网站地址（要求博客地址，请勿提交个人主页）：\n头像图片url（请提供尽可能清晰的图片，我会上传到我自己的图床）：\n描述：\n站点截图（可选）：\n"},peoplecanvas:void 0,postHeadAiDescription:{enable:!0,gptName:"云AI",mode:"tianli",switchBtn:!1,btnLink:"https://afdian.net/item/886a79d4db6711eda42a52540025c377",randomNum:3,basicWordCount:1e3,key:"48580d1e3f53ae174a1e",Referer:"https://blog.tryrun.top"},diytitle:{enable:!0,leaveTitle:"w(ﾟДﾟ)w 不要走！再看看嘛！",backTitle:"♪(^∇^*)欢迎肥来！"},LA51:{enable:!0,ck:"JiFOrFoQklEn9YLS",LingQueMonitorID:"JiqlTmdeI4e1fPbd"},greetingBox:{enable:!0,default:"晚上好👋",list:[{greeting:"晚安😴",startTime:0,endTime:5},{greeting:"早上好鸭👋, 祝你一天好心情！",startTime:6,endTime:9},{greeting:"上午好👋, 状态很好，鼓励一下～",startTime:10,endTime:10},{greeting:"11点多啦, 在坚持一下就吃饭啦～",startTime:11,endTime:11},{greeting:"午安👋, 宝贝",startTime:12,endTime:14},{greeting:"🌈充实的一天辛苦啦！",startTime:14,endTime:18},{greeting:"19点喽, 奖励一顿丰盛的大餐吧🍔。",startTime:19,endTime:19},{greeting:"晚上好👋, 在属于自己的时间好好放松😌~",startTime:20,endTime:24}]},twikooEnvId:"https://twikoo.tryrun.top/",commentBarrageConfig:void 0,root:"/",preloader:{source:2},friends_vue_info:{apiurl:"https://friends.tryrun.top/"},navMusic:!1,mainTone:void 0,authorStatus:{skills:["🤖️ 数码科技爱好者","🔍 分享与热心帮助","🏠 智能家居小能手","🔨 设计开发一条龙","🤝 专修交互与设计","🏃 脚踏实地行动派","🧱 团队小组发动机","💢 壮汉人狠话不多"]},algolia:{appId:"T5VW6VDYLS",apiKey:"227bcb041816af13cb1698db15a8ac89",indexName:"hexo-blog",hits:{per_page:6},languages:{input_placeholder:"输入关键词后按下回车查找",hits_empty:"找不到您查询的内容：${query}",hits_stats:"找到 ${hits} 条结果，用时 ${time} 毫秒"}},localSearch:void 0,translate:{defaultEncoding:2,translateDelay:0,msgToTraditionalChinese:"繁",msgToSimplifiedChinese:"简",rightMenuMsgToTraditionalChinese:"转为繁体",rightMenuMsgToSimplifiedChinese:"转为简体"},noticeOutdate:{limitDay:365,position:"top",messagePrev:"It has been",messageNext:"days since the last update, the content of the article may be outdated."},highlight:{plugin:"highlighjs",highlightCopy:!0,highlightLang:!0,highlightHeightLimit:330},copy:{success:"复制成功",error:"复制错误",noSupport:"浏览器不支持"},relativeDate:{homepage:!0,simplehomepage:!1,post:!0},runtime:"天",date_suffix:{just:"刚刚",min:"分钟前",hour:"小时前",day:"天前",month:"个月前"},copyright:void 0,lightbox:"fancybox",Snackbar:{chs_to_cht:"你已切换为繁体",cht_to_chs:"你已切换为简体",day_to_night:"你已切换为深色模式",night_to_day:"你已切换为浅色模式",bgLight:"#425AEF",bgDark:"#1f1f1f",position:"top-center"},source:{justifiedGallery:{js:"https://cdn.cbd.int/flickr-justified-gallery@2.1.2/dist/fjGallery.min.js",css:"https://cdn.cbd.int/flickr-justified-gallery@2.1.2/dist/fjGallery.css"}},isPhotoFigcaption:!1,islazyload:!0,isAnchor:!1,shortcutKey:void 0,autoDarkmode:!0}</script><script id="config-diff">var GLOBAL_CONFIG_SITE={configTitle:"云少IT",title:"五、GC 性能优化 – GC 算法(实现篇)",postAI:"true - 优化 - 算法",pageFillDescription:"五、GC 性能优化 – GC 算法(实现篇), Serial GC(串行GC), Minor GC(小型GC), Full GC(完全GC), Parallel GC(并行GC), Minor GC(小型GC), Full GC(完全GC), Concurrent Mark and Sweep(并发标记-清除), Minor GC(小型GC), Full GC(完全GC), G1 – Garbage First(垃圾优先算法), Evacuation Pause Fully Young(转移暂停纯年轻代模式), Concurrent Marking(并发标记), Evacuation Pause Mixed (转移暂停 混合模式), 总结, Shenandoah 的性能五性能优化算法实现篇这是我的小站欢迎访问哦学习了算法的相关概念之后我们将介绍在中这些算法的具体实现首先要记住的是大多数都需要使用两种不同的算法一种用来清理年轻代另一种用来清理老年代我们可以选择内置的各种算法如果不通过参数明确指定垃圾收集算法则会使用宿主平台的默认实现本章会详细介绍各种算法的实现原理下面是关于中各种组合的垃圾收集器概要列表对于之前的版本来说可用组合会有一些不同增量看起来有些复杂不用担心主要使用的是上表中黑体字表示的这四种组合其余的要么是被废弃要么是不支持或者是不太适用于生产环境所以接下来我们只介绍下面这些组合及其工作原理年轻代和老年代的串行年轻代和老年代的并行年轻代的并行老年代的负责回收年轻代和老年代串行对年轻代使用标记复制算法对老年代使用标记清除整理算法顾名思义两者都是单线程的垃圾收集器不能进行并行处理两者都会触发全线暂停停止所有的应用线程因此这种算法不能充分利用多核不管有多少内核在垃圾收集时都只能使用单个核心要启用此款收集器只需要指定一个启动参数即可同时对年轻代和老年代生效该选项只适合几百堆内存的而且是单核时比较有用对于服务器端来说因为一般是多个内核并不推荐使用除非确实需要限制所使用的资源大多数服务器端应用部署在多核平台上选择就表示人为的限制系统资源的使用导致的就是资源闲置多的资源也不能用来降低延迟也不能用来增加吞吐量下面让我们看看的垃圾收集日志并从中提取什么有用的信息为了打开日志记录我们使用下面的启动参数产生的日志输出类似这样为了排版已手工折行此日志片段展示了在中发生的很多事情实际上在这段日志中产生了两个事件其中一次清理的是年轻代另一次清理的是整个堆内存让我们先来分析前一次其在年轻代中产生小型以下代码片段展示了清理年轻代内存的事件事件开始的时间其中表示西二时区而中国所在的东区为事件开始时相对于启动时的间隔时间单位是秒用来区分还是的标志表明这是一次小型触发的原因本次事件是由于年轻代中没有空间来存放新的数据结构引起的垃圾收集器的名称这个神秘的名字表示的是在年轻代中使用的单线程标记复制全线暂停垃圾收集器在垃圾收集之前和之后年轻代的使用量年轻代总的空间大小在垃圾收集之前和之后整个堆内存的使用情况可用堆的总空间大小事件持续的时间以秒为单位事件的持续时间通过三个部分来衡量在此次垃圾回收过程中所有线程所消耗的时间之和过程中中操作系统调用和系统等待事件所消耗的时间应用程序暂停的时间因为串行垃圾收集器只使用单线程因此等于和时间的总和可以从上面的日志片段了解到在事件中的内存使用情况发生了怎样的变化此次垃圾收集之前堆内存总的使用量为其中年轻代使用了可以算出老年代使用量为更重要的信息蕴含在下一批数字中垃圾收集之后年轻代的使用量减少了但堆内存的总体使用量只下降了从中可以算出有的对象从年轻代提升到了老年代此次事件也可以用下面的示意图来说明显示的是开始之前以及刚刚结束之后这两个时间点内存使用情况的快照完全理解第一次事件后让我们看看日志中的第二次事件事件开始的时间其中表示西二时区而中国所在的东区为事件开始时相对于启动时的间隔时间单位是秒与上面的示例类似因为内存分配失败在年轻代中发生了一次此次同样使用的是收集器让年轻代的使用量从降为注意对此次的报告有些问题误将年轻代认为是完全填满的此次垃圾收集消耗了秒用于清理老年代空间的垃圾收集器名称表明使用的是单线程的全线暂停垃圾收集器收集算法为标记清除整理在垃圾收集之前和之后老年代的使用量老年代的总空间大小清理老年代所花的时间在垃圾收集之前和之后整个堆内存的使用情况可用堆的总空间大小关于空间同样的信息可以看出此次过程中中没有收集到任何垃圾事件的持续时间通过三个部分来衡量在此次垃圾回收过程中所有线程所消耗的时间之和过程中中操作系统调用和系统等待事件所消耗的时间应用程序暂停的时间因为串行垃圾收集器只使用单线程因此等于和时间的总和和相比最明显的区别是在此次事件中除了年轻代还清理了老年代和在事件开始之前以及刚刚结束之后的内存布局可以用下面的示意图来说明并行并行垃圾收集器这一类组合在年轻代使用标记复制算法在老年代使用标记清除整理算法年轻代和老年代的垃圾回收都会触发事件暂停所有的应用线程来执行垃圾收集两者在执行标记和复制整理阶段时都使用多个线程因此得名通过并行执行使得时间大幅减少通过命令行参数来指定线程数其默认值为内核数可以通过下面的任意一组命令行参数来指定并行并行垃圾收集器适用于多核服务器主要目标是增加吞吐量因为对系统资源的有效使用能达到更高的吞吐量在期间所有内核都在并行清理垃圾所以暂停时间更短在两次周期的间隔期没有线程在运行不会消耗任何系统资源另一方面因为此的所有阶段都不能中断所以并行很容易出现长时间的停顿如果延迟是系统的主要目标那么就应该选择其他垃圾收集器组合译者注长时间卡顿的意思是此启动之后属于一次性完成所有操作于是单次的时间会较长让我们看看并行垃圾收集器的日志长什么样从中我们可以得到哪些有用信息下面的日志中显示了一次暂停和一次暂停小型第一次事件表示发生在年轻代的垃圾收集事件开始的时间其中表示西二时区而中国所在的东区为事件开始时相对于启动时的间隔时间单位是秒用来区分还是的标志表明这是一次小型触发垃圾收集的原因本次事件是由于年轻代中没有适当的空间存放新的数据结构引起的垃圾收集器的名称这个名字表示的是在年轻代中使用的并行的标记复制全线暂停垃圾收集器在垃圾收集之前和之后的年轻代使用量年轻代的总大小在垃圾收集之前和之后整个堆内存的使用量可用堆的总大小事件持续的时间以秒为单位事件的持续时间通过三个部分来衡量在此次垃圾回收过程中由线程所消耗的总的时间过程中中操作系统调用和系统等待事件所消耗的时间应用程序暂停的时间在中这个数字约等于线程数这里使用了个线程请注意总有一定比例的处理过程是不能并行进行的所以可以简单地算出在垃圾收集之前堆内存总使用量为其中年轻代为同时算出老年代使用量为在垃圾收集之后年轻代使用量减少为但总的堆内存使用量只减少了这表示有大小为的对象从年轻代提升到老年代完全学习了并行如何清理年轻代之后下面介绍清理整个堆内存的日志以及如何进行分析事件开始的时间其中表示西二时区而中国所在的东区为事件开始时相对于启动时的间隔时间单位是秒我们可以看到此次事件在前一次完成之后立刻就开始了用来表示此次是的标志表明本次清理的是年轻代和老年代触发垃圾收集的原因表示内部环境认为此时可以进行一次垃圾收集和上面的示例一样清理年轻代的垃圾收集器是名为的收集器采用标记复制算法年轻代使用量从变为一般的结果都是这样用于清理老年代空间的垃圾收集器类型在这里使用的是名为的垃圾收集器这是一款并行垃圾收集器算法为标记清除整理在垃圾收集之前和之后老年代内存的使用情况老年代的总空间大小在垃圾收集之前和之后堆内存的使用情况可用堆内存的总容量类似的信息关于空间的可以看出在事件中里面没有回收任何对象事件持续的时间以秒为单位事件的持续时间通过三个部分来衡量在此次垃圾回收过程中由线程所消耗的总的时间过程中中操作系统调用和系统等待事件所消耗的时间应用程序暂停的时间在中这个数字约等于线程数这里使用了个线程请注意总有一定比例的处理过程是不能并行进行的同样和的区别是很明显的在此次事件中除了年轻代还清理了老年代和在事件前后的内存布局如下图所示并发标记清除的官方名称为主要并发标记清除垃圾收集器其对年轻代采用并行方式的标记复制算法对老年代主要使用并发标记清除算法的设计目标是避免在老年代垃圾收集时出现长时间的卡顿主要通过两种手段来达成此目标第一不对老年代进行整理而是使用空闲列表来管理内存空间的回收第二在标记清除阶段的大部分工作和应用线程一起并发执行也就是说在这些阶段并没有明显的应用线程暂停但值得注意的是它仍然和应用线程争抢时间默认情况下使用的并发线程数等于内核数的通过以下选项来指定垃圾收集器如果服务器是多核并且主要调优目标是降低延迟那么使用是个很明智的选择减少每一次停顿的时间会直接影响到终端用户对系统的体验用户会认为系统非常灵敏因为多数时候都有部分资源被消耗所以在资源受限的情况下会比并行的吞吐量差一些和前面的算法一样我们先来看看算法在实际应用中的日志其中包括一次以及一次停顿小型日志中的第一次事件是清理年轻代的小型让我们来分析垃圾收集器的行为事件开始的时间其中表示西二时区而中国所在的东区为事件开始时相对于启动时的间隔时间单位是秒用来区分还是的标志表明这是一次小型触发垃圾收集的原因本次事件是由于年轻代中没有适当的空间存放新的数据结构引起的垃圾收集器的名称这个名字表示的是在年轻代中使用的并行的标记复制全线暂停垃圾收集器专门设计了用来配合老年代使用的垃圾收集器在垃圾收集之前和之后的年轻代使用量年轻代的总大小垃圾收集器在阶段消耗的时间在垃圾收集之前和之后堆内存的使用情况可用堆的总大小垃圾收集器在标记和复制年轻代存活对象时所消耗的时间包括和收集器的通信开销提升存活时间达标的对象到老年代以及垃圾收集后期的一些最终清理事件的持续时间通过三个部分来衡量在此次垃圾回收过程中由线程所消耗的总的时间过程中中操作系统调用和系统等待事件所消耗的时间应用程序暂停的时间在并行中这个数字约等于线程数这里使用的是个线程请注意总是有固定比例的处理过程是不能并行化的从上面的日志可以看出在之前总的堆内存使用量为年轻代的使用量为这意味着老年代使用量等于之后年轻代的使用量减少了而总的堆内存使用只下降了可以算出有的对象从年轻代提升到老年代完全现在我们已经熟悉了如何解读日志接下来将介绍一种完全不同的日志格式下面这一段很长很长的日志就是对老年代进行垃圾收集时输出的各阶段日志为了简洁我们对这些阶段逐个介绍首先来看收集器整个事件的日志只是要记住在实际情况下进行老年代的并发回收时可能会伴随着多次年轻代的小型在这种情况下大型的日志中就会掺杂着多次小型事件像前面所介绍的一样阶段初始标记这是第一次事件此阶段的目标是标记老年代中所有存活的对象包括的直接引用以及由年轻代中存活对象所引用的对象后者也非常重要因为老年代是独立进行回收的事件开始的时间其中是时区而中国所在的东区为而是相对于启动的时间下面的其他阶段也是一样所以就不再重复介绍垃圾回收的阶段名称为标记所有的老年代的当前使用量老年代中可用内存总量当前堆内存的使用量可用堆的总大小此次暂停的持续时间以和个部分进行衡量阶段并发标记在此阶段垃圾收集器遍历老年代标记所有的存活对象从前一阶段找到的根开始算起顾名思义并发标记阶段就是与应用程序同时运行不用暂停的阶段请注意并非所有老年代中存活的对象都在此阶段被标记因为在标记过程中对象的引用关系还在发生变化在上面的示意图中旁边的一个引用被标记线程并发删除了并发标记是垃圾收集中的一个阶段遍历老年代并标记所有的存活对象此阶段的持续时间分别是运行时间和相应的实际时间这部分对并发阶段来说没多少意义因为是从并发标记开始时计算的而这段时间内不仅并发标记在运行程序也在运行阶段并发预清理此阶段同样是与应用线程并行执行的不需要停止应用线程因为前一阶段是与程序并发进行的可能有一些引用已经改变如果在并发标记过程中发生了引用关系变化会通过将发生了改变的区域标记为脏区这就是所谓的卡片标记在预清理阶段这些脏对象会被统计出来从他们可达的对象也被标记下来此阶段完成后用以标记的也就被清空了此外本阶段也会执行一些必要的细节处理并为阶段做一些准备工作并发预清理阶段统计此前的标记阶段中发生了改变的对象此阶段的持续时间分别是运行时间和对应的实际时间这部分对并发阶段来说没多少意义因为是从并发标记开始时计算的而这段时间内不仅的并发标记在运行程序也在运行阶段并发可取消的预清理此阶段也不停止应用线程本阶段尝试在的之前尽可能地多做一些工作本阶段的具体时间取决于多种因素因为它循环做同样的事情直到满足某个退出条件如迭代次数有用工作量消耗的系统时间等等此阶段的名称此阶段的持续时间运行时间和对应的实际时间有趣的是用户时间明显比时钟时间要小很多通常情况下我们看到的都是时钟时间小于用户时间这意味着因为有一些并行工作所以运行时间才会小于使用的时间这里只进行了少量的工作秒的时间线程经历了很多系统等待从本质上讲线程试图在必须执行暂停之前等待尽可能长的时间默认条件下此阶段可以持续最多秒钟这部分对并发阶段来说没多少意义因为是从并发标记开始时计算的而这段时间内不仅的并发标记线程在运行程序也在运行此阶段可能显著影响停顿的持续时间并且有许多重要的配置选项和失败模式阶段最终标记这是此次事件中第二次也是最后一次阶段本阶段的目标是完成老年代中所有存活对象的标记因为之前的阶段是并发的有可能无法跟上应用程序的变化速度所以需要暂停来处理复杂情况通常会尝试在年轻代尽可能空的情况运行阶段以免接连多次发生事件看起来稍微比之前的阶段要复杂一些事件开始的时间包括时钟时间以及相对于启动的时间其中表示西二时区而中国所在的东区为此阶段的名称为标记老年代中所有存活的对象包括在此前的并发标记过程中创建修改的引用当前年轻代的使用量和总容量在程序暂停时重新进行扫描以完成存活对象的标记此时是并行执行的消耗的时间为秒处理弱引用的第一个子阶段显示的是持续时间和开始时间戳第二个子阶段卸载不使用的类显示的是持续时间和开始的时间戳最后一个子阶段清理持有级别的符号表以及内部化字符串对应的当然也显示了暂停的时钟时间此阶段完成后老年代的使用量和总容量此阶段完成后整个堆内存的使用量和总容量此阶段的持续时间事件的持续时间通过不同的类别来衡量在个标记阶段完成之后老年代中所有的存活对象都被标记了现在将清除所有不使用的对象来回收老年代空间阶段并发清除此阶段与应用程序并发执行不需要停顿目的是删除未使用的对象并收回他们占用的空间此阶段的名称清除未被标记不再使用的对象以释放内存空间此阶段的持续时间分别是运行时间和实际时间部分对并发阶段来说没有多少意义因为是从并发标记开始时计算的而这段时间内不仅是并发标记在运行程序也在运行阶段并发重置此阶段与应用程序并发执行重置算法相关的内部数据为下一次循环做准备此阶段的名称重置算法的内部数据结构为下一次循环做准备此阶段的持续时间分别是运行时间和对应的实际时间部分对并发阶段来说没多少意义因为是从并发标记开始时计算的而这段时间内不仅线程在运行程序也在运行总之垃圾收集器在减少停顿时间上做了很多给力的工作大量的并发线程执行的工作并不需要暂停应用线程当然也有一些缺点其中最大的问题就是老年代内存碎片问题在某些情况下会造成不可预测的暂停时间特别是堆内存较大的情况下垃圾优先算法最主要的设计目标是将停顿的时间和分布变成可预期以及可配置的事实上是一款软实时垃圾收集器也就是说可以为其设置某项特定的性能指标可以指定在任意毫秒的时间范围内停顿不得超过毫秒如任意秒暂停时间不得超过毫秒会尽力达成这个目标有很大的概率会满足但并不完全确定具体是多少将是硬实时的为了达成这项指标有一些独特的实现首先堆不再分成连续的年轻代和老年代空间而是划分为多个通常是个可以存放对象的小堆区每个小堆区都可能是区区或者区在逻辑上所有的区和区合起来就是年轻代所有的区拼在一起那就是老年代这样的划分使得不必每次都去收集整个堆空间而是以增量的方式来处理每次只处理一部分小堆区称为此次的回收集每次暂停都会收集所有年轻代的小堆区但可能只包含一部分老年代小堆区的另一项创新是在并发阶段估算每个小堆区存活对象的总数用来构建回收集的原则是垃圾最多的小堆区会被优先收集这也是名称的由来要启用收集器使用的命令行参数为转移暂停纯年轻代模式在应用程序刚启动时还未执行过并发阶段也就没有获得任何额外的信息处于初始的模式在年轻代空间用满之后应用线程被暂停年轻代堆区中的存活对象被复制到存活区如果还没有存活区则选择任意一部分空闲的小堆区用作存活区复制的过程称为转移这和前面讲过的年轻代收集器基本上是一样的工作原理转移暂停的日志信息很长为简单起见我们去除了一些不重要的信息在并发阶段之后我们会进行详细的讲解此外由于日志记录很多所以并行阶段和其他阶段的日志将拆分为多个部分来进行讲解转移暂停只清理年轻代空间暂停在启动之后开始持续的系统时间为秒表明后面的活动由个线程并行执行消耗时间为毫秒为阅读方便省略了部分内容请参考后文释放用于管理并行活动的内部数据一般都接近于零这是串行执行的过程清理其他部分数据也是非常快的但如非必要则几乎等于零这是串行执行的过程其他活动消耗的时间其中有很多是并行执行的请参考后文暂停之前和暂停之后区的使用量总容量暂停之前和暂停之后存活区的使用量暂停之前和暂停之后整个堆内存的使用量与总容量事件的持续时间通过三个部分来衡量在此次垃圾回收过程中由线程所消耗的总的时间过程中系统调用和系统等待事件所消耗的时间应用程序暂停的时间在并行中这个数字约等于线程数这里使用的是个线程请注意总是有一定比例的处理过程是不能并行化的说明系统时间是指一段程序从运行到终止系统时钟走过的时间一般来说系统时间都是要大于时间最繁重的任务由多个专用的线程来执行下面的日志描述了他们的行为表明下列活动由个线程并行执行消耗的时间为毫秒的线程开始启动时相对于开始的时间戳如果和差别很大则表明本机其他进程所使用的线程数量过多挤占了的时间用了多长时间来扫描堆外的如引用的系统等后面显示了运行时间指的是时间用了多长时间来扫描实际代码中的例如局部变量等等用了多长时间来拷贝收集区内的存活对象的线程用了多长时间来确保自身可以安全地停止这段时间什么也不用做之后该线程就终止运行了的线程尝试多少次和如果发现还有一些任务没处理完则这一次尝试就是失败的暂时还不能终止一些琐碎的小活动在日志中不值得单独列出来的线程的工作时间总计的线程完成作业的时间戳通常来说这部分数字应该大致相等否则就说明有太多的线程被挂起很可能是因为坏邻居效应所导致的此外在转移暂停期间还有一些琐碎执行的小活动这里我们只介绍其中的一部分其余的会在后面进行讨论其他活动消耗的时间其中有很多也是并行执行的处理非强引用的时间进行清理或者决定是否需要清理用来将剩下的引用排列到合适的中将回收集中被释放的小堆归还所消耗的时间以便他们能用来分配新的对象并发标记收集器的很多概念建立在的基础上所以下面的内容需要你对有一定的理解虽然也有很多地方不同但并发标记的目标基本上是一样的的并发标记通过开始时快照的方式在标记阶段开始时记下所有的存活对象即使在标记的同时又有一些变成了垃圾通过对象是存活信息可以构建出每个小堆区的存活状态以便回收集能高效地进行选择这些信息在接下来的阶段会用来执行老年代区域的垃圾收集在两种情况下是完全地并发执行的一如果在标记阶段确定某个小堆区只包含垃圾二在转移暂停期间同时包含垃圾和存活对象的老年代小堆区当堆内存的总体使用比例达到一定数值时就会触发并发标记默认值为但也可以通过参数来设置和一样的并发标记也是由多个阶段组成其中一些是完全并发的还有一些阶段需要暂停应用线程阶段初始标记此阶段标记所有从直接可达的对象在中需要一次暂停但里面通常是在转移暂停的同时处理这些事情所以它的开销是很小的可以在日志中的第一行看到暂停阶段区扫描此阶段标记所有从根区域可达的存活对象根区域包括非空的区域以及在标记过程中不得不收集的区域因为在并发标记的过程中迁移对象会造成很多麻烦所以此阶段必须在下一次转移暂停之前完成如果必须启动转移暂停则会先要求根区域扫描中止等它完成才能继续扫描在当前版本的实现中根区域是存活的小堆区包括下一次转移暂停中肯定会被清理的那部分年轻代小堆区阶段并发标记此阶段非常类似于它只是遍历对象图并在一个特殊的位图中标记能访问到的对象为了确保标记开始时的快照准确性所有应用线程并发对对象图执行的引用更新要求放弃前面阶段为了标记目的而引用的过时引用这是通过使用屏障来实现的不要和之后介绍的混淆也不要和多线程开发中的内存屏障相混淆屏障的作用是在进行并发标记时如果程序将对象的某个属性做了变更就会在中存储之前的引用由并发标记线程负责处理阶段再次标记和类似这也是一次停顿以完成标记过程对于它短暂地停止应用线程停止并发更新日志的写入处理其中的少量信息并标记所有在并发标记开始时未被标记的存活对象这一阶段也执行某些额外的清理如引用处理参见或者类卸载阶段清理最后这个小阶段为即将到来的转移阶段做准备统计小堆区中所有存活的对象并将小堆区进行排序以提升的效率此阶段也为下一次标记执行所有必需的整理工作维护并发标记的内部状态最后要提醒的是所有不包含存活对象的小堆区在此阶段都被回收了有一部分是并发的例如空堆区的回收还有大部分的存活率计算此阶段也需要一个短暂的暂停以不受应用线程的影响来完成作业这种停顿的日志如下如果发现某些小堆区中只包含垃圾则日志格式可能会有点不同如转移暂停混合模式能并发清理老年代中整个整个的小堆区是一种最优情形但有时候并不是这样并发标记完成之后将执行一次混合收集不只清理年轻代还将一部分老年代区域也加入到中混合模式的转移暂停不一定紧跟着并发标记阶段有很多规则和历史数据会影响混合模式的启动时机比如假若在老年代中可以并发地腾出很多的小堆区就没有必要启动混合模式因此在并发标记与混合转移暂停之间很可能会存在多次转移暂停添加到回收集的老年代小堆区的具体数字及其顺序也是基于许多规则来判定的其中包括指定的软实时性能指标存活性以及在并发标记期间收集的效率等数据外加一些可配置的选项混合收集的过程很大程度上和前面的是一样的但这里我们还要介绍一个概念历史记忆集历史记忆集是用来支持不同的小堆区进行独立回收的例如在收集区时我们必须要知道是否有从区或者区指向其中的引用以确定他们的存活性但是遍历整个堆需要相当长的时间这就违背了增量收集的初衷因此必须采取某种优化手段其他算法有独立的来支持年轻代的垃圾收集一样而中使用的是如下图所示每个小堆区都有一个列出了从外部指向本区的所有引用这些引用将被视为附加的注意在并发标记过程中老年代中被确定为垃圾的对象会被忽略即使有外部引用指向他们因为在这种情况下引用者也是垃圾接下来的行为和其他垃圾收集器一样多个线程并行地找出哪些是存活对象确定哪些是垃圾最后存活对象被转移到存活区在必要时会创建新的小堆区现在空的小堆区被释放可用于存放新的对象了为了维护在程序运行的过程中只要写入某个字段就会产生一个屏障如果生成的引用是跨区域的即从一个区指向另一个区就会在目标区的中出现一个对应的条目为了减少造成的开销将卡片放入的过程是异步的而且经过了很多的优化总体上是这样把脏卡信息存放到本地缓冲区有专门的线程负责收集并将相关信息传给被引用区的混合模式下的日志和纯年轻代模式相比可以发现一些有趣的地方因为是并发处理的必须确保在实际的垃圾收集之前缓冲区中的得到处理如果数量很多则并发线程的负载可能就会很高可能的原因是修改的字段过多或者资源受限每个线程处理了多少个本地缓冲区用了多长时间扫描来自的引用清理中的时间清理工作只是简单地删除脏状态此状态用来标识一个字段是否被更新的供使用将中适当的位置标记为所花费的时间适当的位置是由本身执行的堆内存改变所决定的例如引用排队等总结通过本节内容的学习你应该对垃圾收集器有了一定了解当然为了简洁我们省略了很多实现细节例如如何处理巨无霸对象综合来看是中最先进的准产品级垃圾收集器重要的是工程师的主要精力都放在不断改进上面在新的版本中将会带来新的功能和优化可以看到解决了中的各种疑难问题包括暂停时间的可预测性并终结了堆内存的碎片化对单业务延迟非常敏感的系统来说如果资源不受限制那么可以说是中最好的选择特别是在最新版本的虚拟机中当然这种降低延迟的优化也不是没有代价的由于额外的写屏障和更积极的守护线程的开销会更大所以如果系统属于吞吐量优先型的又或者持续占用而又不在乎单次的暂停时间那么是更好的选择总之适合大内存需要低延迟的场景选择正确的算法唯一可行的方式就是去尝试并找出不对劲的地方在下一章我们将给出一般指导原则注意可能会成为的默认的性能译注谢南多厄河情人渡水手谣此款暂时没有标准的中文译名翻译为大水手垃圾收集器我们列出了中可用的所有准生产级算法还有一种还在实验室中的算法称为超低延迟垃圾收集器它的设计目标是管理大型的多核服务器上超大型的堆内存管理及以上的堆容量暂停时间小于当然也是需要和吞吐量进行权衡的没有暂停的时候算法的实现对吞吐量的性能损失不能超过在新算法作为准产品级进行发布之前我们不准备去讨论具体的实现细节但它也构建在前面所提到的很多算法的基础上例如并发标记和增量收集但其中有很多东西是不同的它不再将堆内存划分成多个代而是只采用单个空间没错并不是一款分代垃圾收集器这也就不再需要和它还使用转发指针以及风格的读屏障以允许对存活对象的并发复制从而减少暂停的次数和时间",isPost:!0,isHome:!1,isHighlightShrink:!0,isToc:!0,postUpdate:"2019-01-28 10:19:05",postMainColor:""}</script><noscript><style>#nav{opacity:1}.justified-gallery img{opacity:1}#post-meta time,#recent-posts time{display:inline!important}</style></noscript><script>(e=>{e.saveToLocal={set:(e,t,a)=>{var o;0!==a&&(o=Date.now(),localStorage.setItem(e,JSON.stringify({value:t,expiry:o+864e5*a})))},get:e=>{var t=localStorage.getItem(e);if(t){t=JSON.parse(t);if(!(Date.now()>t.expiry))return t.value;localStorage.removeItem(e)}}},e.getScript=(o,c={})=>new Promise((t,e)=>{const a=document.createElement("script");a.src=o,a.async=!0,a.onerror=e,a.onload=a.onreadystatechange=function(){var e=this.readyState;e&&"loaded"!==e&&"complete"!==e||(a.onload=a.onreadystatechange=null,t())},Object.keys(c).forEach(e=>{a.setAttribute(e,c[e])}),document.head.appendChild(a)}),e.getCSS=(o,c=!1)=>new Promise((t,e)=>{const a=document.createElement("link");a.rel="stylesheet",a.href=o,c&&(a.id=c),a.onerror=e,a.onload=a.onreadystatechange=function(){var e=this.readyState;e&&"loaded"!==e&&"complete"!==e||(a.onload=a.onreadystatechange=null,t())},document.head.appendChild(a)}),e.activateDarkMode=()=>{document.documentElement.setAttribute("data-theme","dark"),null!==document.querySelector('meta[name="theme-color"]')&&document.querySelector('meta[name="theme-color"]').setAttribute("content","#0d0d0d")},e.activateLightMode=()=>{document.documentElement.setAttribute("data-theme","light"),null!==document.querySelector('meta[name="theme-color"]')&&document.querySelector('meta[name="theme-color"]').setAttribute("content","#ffffff")};var e=saveToLocal.get("theme"),t=window.matchMedia("(prefers-color-scheme: dark)").matches,a=window.matchMedia("(prefers-color-scheme: light)").matches,o=window.matchMedia("(prefers-color-scheme: no-preference)").matches,c=!t&&!a&&!o,t=(void 0===e?(a?activateLightMode():t?activateDarkMode():(o||c)&&((a=(new Date).getHours())<=6||18<=a?activateDarkMode:activateLightMode)(),window.matchMedia("(prefers-color-scheme: dark)").addListener(e=>{void 0===saveToLocal.get("theme")&&(e.matches?activateDarkMode:activateLightMode)()})):("light"===e?activateLightMode:activateDarkMode)(),saveToLocal.get("aside-status"));void 0!==t&&("hide"===t?document.documentElement.classList.add("hide-aside"):document.documentElement.classList.remove("hide-aside"));/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)&&document.documentElement.classList.add("apple")})(window)</script><link rel="stylesheet" href="/css/1.min.css?1" media="async" onload='this.media="all"'><link rel="stylesheet" href="/css/bg.css?1" media="async" onload='this.media="all"'><meta name="generator" content="Hexo 6.3.0"><link rel="alternate" href="/atom.xml" title="云少IT" type="application/atom+xml"><link rel="alternate" href="/rss.xml" title="云少IT" type="application/rss+xml"></head><body data-type="anzhiyu"><div id="web_bg"></div><div id="an_music_bg"></div><link rel="stylesheet" href="https://cdn.cbd.int/anzhiyu-theme-static@1.1.10/progress_bar/progress_bar.css"><script async src="https://cdn.cbd.int/pace-js@1.2.4/pace.min.js" data-pace-options="{ &quot;restartOnRequestAfter&quot;:false,&quot;eventLag&quot;:false}"></script><div class="post" id="body-wrap"><header class="post-bg" id="page-header"><nav id="nav"><div id="nav-group"><span id="blog_name"><div class="back-home-button"><i class="anzhiyufont anzhiyu-icon-grip-vertical"></i><div class="back-menu-list-groups"><div class="back-menu-list-group"><div class="back-menu-list-title">网页</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top/" title="博客"><img class="back-menu-item-icon" src="/img/favicon.ico" alt="博客"><span class="back-menu-item-text">博客</span></a></div></div><div class="back-menu-list-group"><div class="back-menu-list-title">项目</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top" title="图床"><img class="back-menu-item-icon" src="https://www.tryrun.top/favicon.ico" alt="图床"><span class="back-menu-item-text">图床</span></a></div></div></div></div><a id="site-name" href="/" accesskey="h"><div class="title">云少IT</div><i class="anzhiyufont anzhiyu-icon-house-chimney"></i></a></span><div class="mask-name-container"><div id="name-container"><a id="page-name" href="javascript:anzhiyu.scrollToDest(0, 500)" rel="external nofollow noreferrer">PAGE_NAME</a></div></div><div id="menus"><div class="menus_items"><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>望四方</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/archives/"><i class="anzhiyufont anzhiyu-icon-box-archive faa-tada" style="font-size:.9em"></i><span> 归名档</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/categories/"><i class="anzhiyufont anzhiyu-icon-shapes faa-tada" style="font-size:.9em"></i><span> 归四类</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/tags/"><i class="anzhiyufont anzhiyu-icon-tags faa-tada" style="font-size:.9em"></i><span> 书中签</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/charts/"><i class="fa-fw fas fa-chart-bar faa-tada"></i><span> 统计</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>友链</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/link/"><i class="anzhiyufont anzhiyu-icon-link faa-tada" style="font-size:.9em"></i><span> 四方好友</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/fcircle/"><i class="anzhiyufont anzhiyu-icon-artstation faa-tada" style="font-size:.9em"></i><span> 朋友圈</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/comments/"><i class="anzhiyufont anzhiyu-icon-envelope faa-tada" style="font-size:.9em"></i><span> 留言板</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>我的</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/bangumis/"><i class="anzhiyufont anzhiyu-icon-bilibili faa-tada" style="font-size:.9em"></i><span> 追番页</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/album/"><i class="anzhiyufont anzhiyu-icon-images faa-tada" style="font-size:.9em"></i><span> 刹那间</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/equipment/"><i class="fas fa-heart faa-tada"></i><span> 我的装备</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/collect/"><i class="fas fa-camcorder faa-tada"></i><span> 观影阁</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>关于</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/about/"><i class="anzhiyufont anzhiyu-icon-paper-plane faa-tada" style="font-size:.9em"></i><span> 关于本人</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/essay/"><i class="anzhiyufont anzhiyu-icon-lightbulb faa-tada" style="font-size:.9em"></i><span> 闲言碎语</span></a></li><li><a class="site-page child faa-parent animated-hover" href="javascript:toRandomPost()" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-shoe-prints1 faa-tada" style="font-size:.9em"></i><span> 随便逛逛</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/disclaimer/"><i class="fas fa-heart faa-tada"></i><span> 免责声明</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/love/"><i class="anzhiyufont anzhiyu-icon-heartbeat faa-tada" style="font-size:.9em"></i><span> 恋爱小屋</span></a></li></ul></div></div></div><div id="nav-right"><div class="nav-button only-home" id="travellings_button" title="随机前往一个开往项目网站"><a class="site-page" onclick="anzhiyu.totraveling()" title="随机前往一个开往项目网站" href="javascript:void(0);" rel="external nofollow" data-pjax-state="external"><i class="anzhiyufont anzhiyu-icon-train"></i></a></div><div class="nav-button" id="randomPost_button"><a class="site-page" onclick="toRandomPost()" title="随机前往一个文章" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-dice"></i></a></div><div class="nav-button" id="search-button"><a class="site-page social-icon search" href="javascript:void(0);" rel="external nofollow noreferrer" title="搜索🔍" accesskey="s"><i class="anzhiyufont anzhiyu-icon-magnifying-glass"></i><span> 搜索</span></a></div><input id="center-console" type="checkbox"><label class="widget" for="center-console" title="中控台" onclick="anzhiyu.switchConsole()"><i class="left"></i><i class="widget center"></i><i class="widget right"></i></label><div id="console"><div class="console-card-group-reward"><ul class="reward-all console-card"><li class="reward-item"><a href="/img/wxpay.webp" target="_blank"><img class="post-qr-code-img" alt="wechat" src="/img/wxpay.webp"></a><div class="post-qr-code-desc">wechat</div></li><li class="reward-item"><a href="/img/alipay.webp" target="_blank"><img class="post-qr-code-img" alt="alipay" src="/img/alipay.webp"></a><div class="post-qr-code-desc">alipay</div></li></ul></div><div class="console-card-group"><div class="console-card-group-left"><div class="console-card" id="card-newest-comments"><div class="card-content"><div class="author-content-item-tips">互动</div><span class="author-content-item-title">最新评论</span></div><div class="aside-list"><span>正在加载中...</span></div></div></div><div class="console-card-group-right"><div class="console-card tags"><div class="card-content"><div class="author-content-item-tips">兴趣点</div><span class="author-content-item-title">寻找你感兴趣的领域</span><div class="card-tags"><div class="item-headline"></div><div class="card-tag-cloud"><a href="/tags/API/" style="font-size:1.05rem;color:#637571">API<sup>43</sup></a><a href="/tags/Base64/" style="font-size:1.05rem;color:#8a0460">Base64<sup>1</sup></a><a href="/tags/Collectors/" style="font-size:1.05rem;color:#6b3641">Collectors<sup>3</sup></a><a href="/tags/Date/" style="font-size:1.05rem;color:#5e6603">Date<sup>3</sup></a><a href="/tags/Executor/" style="font-size:1.05rem;color:#abb61f">Executor<sup>9</sup></a><a href="/tags/Guava/" style="font-size:1.05rem;color:#6dc55c">Guava<sup>1</sup></a><a href="/tags/JVM/" style="font-size:1.05rem;color:#8745c2">JVM<sup>8</sup></a><a href="/tags/Java8/" style="font-size:1.05rem;color:#804042">Java8<sup>21</sup></a><a href="/tags/Java9/" style="font-size:1.05rem;color:#21b4c6">Java9<sup>21</sup></a><a href="/tags/Java%E5%B9%B6%E5%8F%91/" style="font-size:1.05rem;color:#0a3988">Java并发<sup>20</sup></a><a href="/tags/Lambda/" style="font-size:1.05rem;color:#29446d">Lambda<sup>4</sup></a><a href="/tags/Lock/" style="font-size:1.05rem;color:#afc378">Lock<sup>1</sup></a><a href="/tags/Maven/" style="font-size:1.05rem;color:#b10843">Maven<sup>1</sup></a><a href="/tags/Memcached/" style="font-size:1.05rem;color:#9663a4">Memcached<sup>23</sup></a><a href="/tags/Mongodb/" style="font-size:1.05rem;color:#b4214d">Mongodb<sup>49</sup></a><a href="/tags/Queue/" style="font-size:1.05rem;color:#220f01">Queue<sup>1</sup></a><a href="/tags/Redis/" style="font-size:1.05rem;color:#c4106c">Redis<sup>27</sup></a><a href="/tags/Stream/" style="font-size:1.05rem;color:#1f5f9f">Stream<sup>4</sup></a><a href="/tags/Thread/" style="font-size:1.05rem;color:#c3563b">Thread<sup>7</sup></a><a href="/tags/Thread-pool/" style="font-size:1.05rem;color:#1a3364">Thread pool<sup>7</sup></a><a href="/tags/forkJoinPool/" style="font-size:1.05rem;color:#1ea132">forkJoinPool<sup>2</sup></a><a href="/tags/stream/" style="font-size:1.05rem;color:#a00679">stream<sup>1</sup></a><a href="/tags/%E4%B8%93%E6%A0%8F/" style="font-size:1.05rem;color:#9e5e9b">专栏<sup>35</sup></a><a href="/tags/%E4%BA%8B%E5%8A%A1/" style="font-size:1.05rem;color:#4d47bc">事务<sup>1</sup></a><a href="/tags/%E4%BC%98%E5%8C%96/" style="font-size:1.05rem;color:#a56245">优化<sup>1</sup></a><a href="/tags/%E5%91%BD%E4%BB%A4/" style="font-size:1.05rem;color:#9c8435">命令<sup>57</sup></a><a href="/tags/%E5%AE%89%E8%A3%85/" style="font-size:1.05rem;color:#647e28">安装<sup>6</sup></a><a href="/tags/%E5%B7%A5%E5%85%B7/" style="font-size:1.05rem;color:#a192b9">工具<sup>2</sup></a><a href="/tags/%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/" style="font-size:1.05rem;color:#22a370">数据类型<sup>8</sup></a><a href="/tags/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/" style="font-size:1.05rem;color:#6b985c">生命周期<sup>1</sup></a><a href="/tags/%E7%AE%80%E4%BB%8B/" style="font-size:1.05rem;color:#bc5f40">简介<sup>7</sup></a><a href="/tags/%E7%AE%97%E6%B3%95/" style="font-size:1.05rem;color:#167366">算法<sup>10</sup></a><a href="/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" style="font-size:1.05rem;color:#aa928f">设计模式<sup>38</sup></a><a href="/tags/%E9%85%8D%E7%BD%AE/" style="font-size:1.05rem;color:#3a740b">配置<sup>2</sup></a><a href="/tags/%E9%9D%A2%E8%AF%95/" style="font-size:1.05rem;color:#75c343">面试<sup>11</sup></a></div></div><hr></div></div><div class="console-card history"><div class="item-headline"><i class="anzhiyufont anzhiyu-icon-box-archiv"></i><span>文章</span></div><div class="card-archives"><div class="item-headline"><i class="anzhiyufont anzhiyu-icon-archive"></i><span>归档</span><a class="card-more-btn" href="/archives/" title="查看更多"> <i class="anzhiyufont anzhiyu-icon-angle-right"></i></a></div><ul class="card-archive-list"><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2023/06/"><span class="card-archive-list-date">六月 2023</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">2</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2021/05/"><span class="card-archive-list-date">五月 2021</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">2</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/10/"><span class="card-archive-list-date">十月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">21</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/09/"><span class="card-archive-list-date">九月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">44</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/08/"><span class="card-archive-list-date">八月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">47</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/07/"><span class="card-archive-list-date">七月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">42</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/06/"><span class="card-archive-list-date">六月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">31</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/05/"><span class="card-archive-list-date">五月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">43</span><span>篇</span></div></a></li></ul></div><hr></div></div></div><div class="button-group"><div class="console-btn-item"><a class="darkmode_switchbutton" title="显示模式切换" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-moon"></i></a></div><div class="console-btn-item" id="consoleHideAside" onclick="anzhiyu.hideAsideBtn()" title="边栏显示控制"><a class="asideSwitch"><i class="anzhiyufont anzhiyu-icon-arrows-left-right"></i></a></div><div class="console-btn-item on" id="consoleCommentBarrage" onclick="anzhiyu.switchCommentBarrage()" title="热评开关"><a class="commentBarrage"><i class="anzhiyufont anzhiyu-icon-message"></i></a></div><div class="console-btn-item" id="consoleMusic" onclick="anzhiyu.musicToggle()" title="音乐开关"><a class="music-switch"><i class="anzhiyufont anzhiyu-icon-music"></i></a></div></div><div class="console-mask" onclick="anzhiyu.hideConsole()" href="javascript:void(0);" rel="external nofollow noreferrer"></div></div><div class="nav-button" id="nav-totop"><a class="totopbtn" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-arrow-up"></i><span id="percent" onclick="anzhiyu.scrollToDest(0,500)">0</span></a></div><div id="toggle-menu"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer" title="切换"><i class="anzhiyufont anzhiyu-icon-bars"></i></a></div></div></div></nav><div id="post-info"><div id="post-firstinfo"><div class="meta-firstline"><a class="post-meta-original">原创</a><span class="article-meta tags"><a class="article-meta__tags" href="/tags/JVM/" tabindex="-1" itemprop="url"> <span><i class="anzhiyufont anzhiyu-icon-hashtag"></i>JVM</span></a></span></div></div><h1 class="post-title" itemprop="name headline">五、GC 性能优化 – GC 算法(实现篇)</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="anzhiyufont anzhiyu-icon-calendar-days post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" itemprop="dateCreated datePublished" datetime="2019-01-28T02:19:05.000Z" title="发表于 2019-01-28 10:19:05">2019-01-28</time><span class="post-meta-separator"></span><i class="anzhiyufont anzhiyu-icon-history post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" itemprop="dateCreated datePublished" datetime="2019-01-28T02:19:05.000Z" title="更新于 2019-01-28 10:19:05">2019-01-28</time></span></div><div class="meta-secondline"><span class="post-meta-separator"></span><span class="post-meta-wordcount"><i class="anzhiyufont anzhiyu-icon-file-word post-meta-icon" title="文章字数"></i><span class="post-meta-label" title="文章字数">字数总计:</span><span class="word-count" title="文章字数">13.7k</span><span class="post-meta-separator"></span><i class="anzhiyufont anzhiyu-icon-clock post-meta-icon" title="阅读时长"></i><span class="post-meta-label" title="阅读时长">阅读时长:</span><span>51分钟</span></span><span class="post-meta-separator"></span><span class="post-meta-pv-cv" data-flag-title="五、GC 性能优化 – GC 算法(实现篇)"><i class="anzhiyufont anzhiyu-icon-fw-eye post-meta-icon"></i><span class="post-meta-label" title="阅读量">阅读量:</span><span id="busuanzi_value_page_pv"><i class="anzhiyufont anzhiyu-icon-spinner anzhiyu-spin"></i></span></span><span class="post-meta-separator"> </span><span class="post-meta-position" title="作者IP属地为武汉"><i class="anzhiyufont anzhiyu-icon-location-dot"></i>武汉</span></div></div></div><section class="main-hero-waves-area waves-area"><svg class="waves-svg" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto"><defs><path id="gentle-wave" d="M -160 44 c 30 0 58 -18 88 -18 s 58 18 88 18 s 58 -18 88 -18 s 58 18 88 18 v 44 h -352 Z"></path></defs><g class="parallax"><use href="#gentle-wave" x="48" y="0"></use><use href="#gentle-wave" x="48" y="3"></use><use href="#gentle-wave" x="48" y="5"></use><use href="#gentle-wave" x="48" y="7"></use></g></svg></section><div id="post-top-cover"><img class="nolazyload" id="post-top-bg" src="https://cn.bing.com/th?id=OHR.NewtonPumpkins_EN-US4897949591_UHD.jpg"></div></header><main id="blog-container"><div class="layout" id="content-inner"><div id="post"><div class="post-ai-description"><div class="ai-title"><i class="anzhiyufont anzhiyu-icon-bilibili"></i><div class="ai-title-text">AI-摘要</div><i class="anzhiyufont anzhiyu-icon-arrow-rotate-right"></i><i class="anzhiyufont anzhiyu-icon-circle-dot" title="朗读摘要"></i><div id="ai-tag">Tianli GPT</div></div><div class="ai-explanation">AI初始化中...</div><div class="ai-btn-box"><div class="ai-btn-item">介绍自己 🙈</div><div class="ai-btn-item">生成本文简介 👋</div><div class="ai-btn-item">推荐相关文章 📖</div><div class="ai-btn-item">前往主页 🏠</div><div class="ai-btn-item" id="go-tianli-blog">前往爱发电购买</div></div><script data-pjax src="/js/anzhiyu/ai_abstract.js"></script></div><article class="post-content" id="article-container" itemscope itemtype="https://it985.github.io/posts/46b81885.html"><header><a href="/tags/JVM/" tabindex="-1" itemprop="url">JVM</a><h1 id="CrawlerTitle" itemprop="name headline">五、GC 性能优化 – GC 算法(实现篇)</h1><span itemprop="author" itemscope itemtype="http://schema.org/Person">云少</span><time itemprop="dateCreated datePublished" datetime="2019-01-28T02:19:05.000Z" title="发表于 2019-01-28 10:19:05">2019-01-28</time><time itemprop="dateCreated datePublished" datetime="2019-01-28T02:19:05.000Z" title="更新于 2019-01-28 10:19:05">2019-01-28</time></header><h1 id="五、GC-性能优化-–-GC-算法-实现篇"><a href="#五、GC-性能优化-–-GC-算法-实现篇" class="headerlink" title="五、GC 性能优化 – GC 算法(实现篇)"></a>五、GC 性能优化 – GC 算法(实现篇)</h1><blockquote><p>master ，这是我的<a target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top/">小站</a>,欢迎访问哦~~</p></blockquote><p>学习了GC算法的相关概念之后, 我们将介绍在JVM中这些算法的具体实现。首先要记住的是, 大多数JVM都需要使用两种不同的GC算法 —— 一种用来清理年轻代, 另一种用来清理老年代。</p><p>我们可以选择JVM内置的各种算法。如果不通过参数明确指定垃圾收集算法, 则会使用宿主平台的默认实现。本章会详细介绍各种算法的实现原理。</p><p>下面是关于Java 8中各种组合的垃圾收集器概要列表,对于之前的Java版本来说,可用组合会有一些不同:</p><table><thead><tr><th align="center"><strong>Young</strong></th><th align="center"><strong>Tenured</strong></th><th align="center"><strong>JVM options</strong></th></tr></thead><tbody><tr><td align="center">Incremental(增量GC)</td><td align="center">Incremental</td><td align="center">-Xincgc</td></tr><tr><td align="center"><strong>Serial</strong></td><td align="center"><strong>Serial</strong></td><td align="center"><strong>-XX:+UseSerialGC</strong></td></tr><tr><td align="center">Parallel Scavenge</td><td align="center">Serial</td><td align="center">-XX:+UseParallelGC -XX:-UseParallelOldGC</td></tr><tr><td align="center">Parallel New</td><td align="center">Serial</td><td align="center">N&#x2F;A</td></tr><tr><td align="center">Serial</td><td align="center">Parallel Old</td><td align="center">N&#x2F;A</td></tr><tr><td align="center"><strong>Parallel Scavenge</strong></td><td align="center"><strong>Parallel Old</strong></td><td align="center"><strong>-XX:+UseParallelGC -XX:+UseParallelOldGC</strong></td></tr><tr><td align="center">Parallel New</td><td align="center">Parallel Old</td><td align="center">N&#x2F;A</td></tr><tr><td align="center">Serial</td><td align="center">CMS</td><td align="center">-XX:-UseParNewGC -XX:+UseConcMarkSweepGC</td></tr><tr><td align="center">Parallel Scavenge</td><td align="center">CMS</td><td align="center">N&#x2F;A</td></tr><tr><td align="center"><strong>Parallel New</strong></td><td align="center"><strong>CMS</strong></td><td align="center"><strong>-XX:+UseParNewGC -XX:+UseConcMarkSweepGC</strong></td></tr><tr><td align="center"><strong>G1</strong></td><td align="center"><strong>-XX:+UseG1GC</strong></td><td align="center"></td></tr></tbody></table><p>看起来有些复杂, 不用担心。主要使用的是上表中黑体字表示的这四种组合。其余的要么是<a target="_blank" rel="noopener external nofollow noreferrer" href="http://openjdk.java.net/jeps/173">被废弃(deprecated)</a>, 要么是不支持或者是不太适用于生产环境。所以,接下来我们只介绍下面这些组合及其工作原理:</p><ul><li>年轻代和老年代的串行GC(Serial GC)</li><li>年轻代和老年代的并行GC(Parallel GC)</li><li>年轻代的并行GC(Parallel New) + 老年代的CMS(Concurrent Mark and Sweep)</li><li>G1, 负责回收年轻代和老年代</li></ul><h2 id="Serial-GC-串行GC"><a href="#Serial-GC-串行GC" class="headerlink" title="Serial GC(串行GC)"></a>Serial GC(串行GC)</h2><p>Serial GC 对年轻代使用 <a target="_blank" rel="noopener external nofollow noreferrer" href="https://plumbr.eu/handbook/garbage-collection-algorithms/removing-unused-objects/copy">mark-copy(标记-复制) 算法</a>, 对老年代使用 <a target="_blank" rel="noopener external nofollow noreferrer" href="https://plumbr.eu/handbook/garbage-collection-algorithms/removing-unused-objects/compact">mark-sweep-compact(标记-清除-整理)算法</a>. 顾名思义, 两者都是单线程的垃圾收集器,不能进行并行处理。两者都会触发全线暂停(STW),停止所有的应用线程。</p><p>因此这种GC算法不能充分利用多核CPU。不管有多少CPU内核, JVM 在垃圾收集时都只能使用单个核心。</p><p>要启用此款收集器, 只需要指定一个JVM启动参数即可,同时对年轻代和老年代生效:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -XX:+UseSerialGC com.mypackages.MyExecutableClass</span><br></pre></td></tr></table></figure><p>该选项只适合几百MB堆内存的JVM,而且是单核CPU时比较有用。 对于服务器端来说, 因为一般是多个CPU内核, 并不推荐使用, 除非确实需要限制JVM所使用的资源。大多数服务器端应用部署在多核平台上, 选择 Serial GC 就表示人为的限制系统资源的使用。 导致的就是资源闲置, 多的CPU资源也不能用来降低延迟,也不能用来增加吞吐量。</p><p>下面让我们看看Serial GC的垃圾收集日志, 并从中提取什么有用的信息。为了打开GC日志记录, 我们使用下面的JVM启动参数:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">-XX:+PrintGCDetails -XX:+PrintGCDateStamps </span><br><span class="line">-XX:+PrintGCTimeStamps</span><br></pre></td></tr></table></figure><p>产生的GC日志输出类似这样(为了排版,已手工折行):</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">2022-05-09T14:45:37.987-0200: </span><br><span class="line">        151.126: [GC (Allocation Failure) </span><br><span class="line">        151.126: [DefNew: 629119K-&gt;69888K(629120K), 0.0584157 secs] </span><br><span class="line">        1619346K-&gt;1273247K(2027264K), 0.0585007 secs]</span><br><span class="line">    [Times: user=0.06 sys=0.00, real=0.06 secs]</span><br><span class="line">2022-05-09T14:45:59.690-0200: </span><br><span class="line">        172.829: [GC (Allocation Failure) </span><br><span class="line">        172.829: [DefNew: 629120K-&gt;629120K(629120K), 0.0000372 secs]</span><br><span class="line">        172.829: [Tenured: 1203359K-&gt;755802K(1398144K), 0.1855567 secs] </span><br><span class="line">        1832479K-&gt;755802K(2027264K), </span><br><span class="line">        [Metaspace: 6741K-&gt;6741K(1056768K)], 0.1856954 secs] </span><br><span class="line">    [Times: user=0.18 sys=0.00, real=0.18 secs]</span><br></pre></td></tr></table></figure><p>此GC日志片段展示了在JVM中发生的很多事情。 实际上,在这段日志中产生了两个GC事件, 其中一次清理的是年轻代,另一次清理的是整个堆内存。让我们先来分析前一次GC,其在年轻代中产生。</p><h3 id="Minor-GC-小型GC"><a href="#Minor-GC-小型GC" class="headerlink" title="Minor GC(小型GC)"></a>Minor GC(小型GC)</h3><p>以下代码片段展示了清理年轻代内存的GC事件:</p><blockquote><p><code>2022-05-09T14:45:37.987-0200</code>1 : <code>151.1262</code>2 : [ <code>GC</code>3 (<code>Allocation Failure</code>4 151.126:</p><p>[<code>DefNew</code>5 : <code>629119K-&gt;69888K</code>6 <code>(629120K)</code>7 , 0.0584157 secs] <code>1619346K-&gt;1273247K</code>8</p><p><code>(2027264K)</code>9, <code>0.0585007 secs</code>10] <code>[Times: user=0.06 sys=0.00, real=0.06 secs]</code>11</p><p>1、 <code>2022-05-09T14:45:37.987-0200</code> – GC事件开始的时间. 其中<code>-0200</code>表示西二时区,而中国所在的东8区为 <code>+0800</code>。</p><p>2、 <code>151.126</code> – GC事件开始时,相对于JVM启动时的间隔时间,单位是秒。</p><p>3、 <code>GC</code> – 用来区分 Minor GC 还是 Full GC 的标志。<code>GC</code>表明这是一次<strong>小型GC</strong>(Minor GC)</p><p>4、 <code>Allocation Failure</code> – 触发 GC 的原因。本次GC事件, 是由于年轻代中没有空间来存放新的数据结构引起的。</p><p>5、 <code>DefNew</code> – 垃圾收集器的名称。这个神秘的名字表示的是在年轻代中使用的: 单线程, 标记-复制(mark-copy), 全线暂停(STW) 垃圾收集器。</p><p>6、 <code>629119K-&gt;69888K</code> – 在垃圾收集之前和之后年轻代的使用量。</p><p>7、 <code>(629120K)</code> – 年轻代总的空间大小。</p><p>8、 <code>1619346K-&gt;1273247K</code> – 在垃圾收集之前和之后整个堆内存的使用情况。</p><p>9、 <code>(2027264K)</code> – 可用堆的总空间大小。</p><p>10、 <code>0.0585007 secs</code> – GC事件持续的时间,以秒为单位。</p><p>11、 <code>[Times: user=0.06 sys=0.00, real=0.06 secs]</code> – GC事件的持续时间, 通过三个部分来衡量:</p><ul><li>user – 在此次垃圾回收过程中, 所有 GC线程所消耗的CPU时间之和。</li><li>sys – GC过程中中操作系统调用和系统等待事件所消耗的时间。</li><li>real – 应用程序暂停的时间。因为串行垃圾收集器(Serial Garbage Collector)只使用单线程, 因此 real time 等于 user 和 system 时间的总和。</li></ul></blockquote><p>可以从上面的日志片段了解到, 在GC事件中,JVM 的内存使用情况发生了怎样的变化。此次垃圾收集之前, 堆内存总的使用量为 <strong>1,619,346K</strong>。其中,年轻代使用了 <strong>629,119K</strong>。可以算出,老年代使用量为 <strong>990,227K</strong>。</p><p>更重要的信息蕴含在下一批数字中, 垃圾收集之后, 年轻代的使用量减少了 <strong>559,231K</strong>, 但堆内存的总体使用量只下降了 <strong>346,099K</strong>。 从中可以算出,有 <strong>213,132K</strong> 的对象从年轻代提升到了老年代。</p><p>此次GC事件也可以用下面的示意图来说明, 显示的是GC开始之前, 以及刚刚结束之后, 这两个时间点内存使用情况的快照:</p><p><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://s3.uuu.ovh/imgs/2022/05/08/42547ba597009f1a.png" alt="img_1.png"></p><h3 id="Full-GC-完全GC"><a href="#Full-GC-完全GC" class="headerlink" title="Full GC(完全GC)"></a>Full GC(完全GC)</h3><p>理解第一次 minor GC 事件后,让我们看看日志中的第二次GC事件:</p><blockquote><p><code>2022-05-09T14:45:59.690-0200</code>1 : <code>172.829</code>2 : [GC (Allocation Failure 172.829:</p><p><code>[DefNew: 629120K-&gt;629120K(629120K), 0.0000372 secs</code>3] 172.829:[<code>Tenured</code>4:</p><p><code>1203359K-&gt;755802K</code>5 <code>(1398144K)</code>6, <code>0.1855567 secs</code>7 ] <code>1832479K-&gt;755802K</code>8</p><p><code>(2027264K)</code>9, <code>[Metaspace: 6741K-&gt;6741K(1056768K)]</code>10</p><p><code>[Times: user=0.18 sys=0.00, real=0.18 secs]</code>11</p><p>1、 <code>2022-05-09T14:45:59.690-0200</code> – GC事件开始的时间. 其中<code>-0200</code>表示西二时区,而中国所在的东8区为 <code>+0800</code>。</p><p>2、 <code>172.829</code> – GC事件开始时,相对于JVM启动时的间隔时间,单位是秒。</p><p>3、 <code>[DefNew: 629120K-&gt;629120K(629120K), 0.0000372 secs</code> – 与上面的示例类似, 因为内存分配失败,在年轻代中发生了一次 minor GC。此次GC同样使用的是 DefNew 收集器, 让年轻代的使用量从 629120K 降为 0。注意,JVM对此次GC的报告有些问题,误将年轻代认为是完全填满的。此次垃圾收集消耗了 0.0000372秒。</p><p>4、 <code>Tenured</code> – 用于清理老年代空间的垃圾收集器名称。<strong>Tenured</strong> 表明使用的是单线程的全线暂停垃圾收集器, 收集算法为 标记-清除-整理(mark-sweep-compact )。</p><p>5、 <code>1203359K-&gt;755802K</code> – 在垃圾收集之前和之后老年代的使用量。</p><p>6、 <code>(1398144K)</code> – 老年代的总空间大小。</p><p>7、 <code>0.1855567 secs</code> – 清理老年代所花的时间。</p><p>8、 <code>1832479K-&gt;755802K</code> – 在垃圾收集之前和之后,整个堆内存的使用情况。</p><p>9、 <code>(2027264K)</code> – 可用堆的总空间大小。</p><p>10、 <code>[Metaspace: 6741K-&gt;6741K(1056768K)]</code> – 关于 Metaspace 空间, 同样的信息。可以看出, 此次GC过程中 Metaspace 中没有收集到任何垃圾。</p><p>11、 <code>[Times: user=0.18 sys=0.00, real=0.18 secs]</code> – GC事件的持续时间, 通过三个部分来衡量:<br>* user – 在此次垃圾回收过程中, 所有 GC线程所消耗的CPU时间之和。<br>* sys – GC过程中中操作系统调用和系统等待事件所消耗的时间。<br>* real – 应用程序暂停的时间。因为串行垃圾收集器(Serial Garbage Collector)只使用单线程, 因此 real time 等于 user 和 system 时间的总和。</p></blockquote><p>和 Minor GC 相比,最明显的区别是 —— 在此次GC事件中, 除了年轻代, 还清理了老年代和Metaspace. 在GC事件开始之前, 以及刚刚结束之后的内存布局,可以用下面的示意图来说明:</p><p><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://s3.uuu.ovh/imgs/2022/05/08/411c683328558fc0.png" alt="img_2.png"></p><h2 id="Parallel-GC-并行GC"><a href="#Parallel-GC-并行GC" class="headerlink" title="Parallel GC(并行GC)"></a>Parallel GC(并行GC)</h2><p>并行垃圾收集器这一类组合, 在年轻代使用 <a target="_blank" rel="noopener external nofollow noreferrer" href="https://plumbr.eu/handbook/garbage-collection-algorithms/removing-unused-objects/copy">标记-复制(mark-copy)算法</a>, 在老年代使用 <a target="_blank" rel="noopener external nofollow noreferrer" href="https://plumbr.eu/handbook/garbage-collection-algorithms/removing-unused-objects/compact">标记-清除-整理(mark-sweep-compact)算法</a>。年轻代和老年代的垃圾回收都会触发STW事件,暂停所有的应用线程来执行垃圾收集。两者在执行 标记和 复制&#x2F;整理阶段时都使用多个线程, 因此得名“**(Parallel)**”。通过并行执行, 使得GC时间大幅减少。</p><p>通过命令行参数 <code>-XX:ParallelGCThreads=NNN</code> 来指定 GC 线程数。 其默认值为CPU内核数。</p><p>可以通过下面的任意一组命令行参数来指定并行GC:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">java -XX:+UseParallelGC com.mypackages.MyExecutableClass</span><br><span class="line">java -XX:+UseParallelOldGC com.mypackages.MyExecutableClass</span><br><span class="line">java -XX:+UseParallelGC -XX:+UseParallelOldGC com.mypackages.MyExecutableClass</span><br></pre></td></tr></table></figure><p>并行垃圾收集器适用于多核服务器,主要目标是增加吞吐量。因为对系统资源的有效使用,能达到更高的吞吐量:</p><ul><li>在GC期间, 所有 CPU 内核都在并行清理垃圾, 所以暂停时间更短</li><li>在两次GC周期的间隔期, 没有GC线程在运行,不会消耗任何系统资源</li></ul><p>另一方面, 因为此GC的所有阶段都不能中断, 所以并行GC很容易出现长时间的停顿. 如果延迟是系统的主要目标, 那么就应该选择其他垃圾收集器组合。</p><blockquote><p><strong>译者注</strong>: 长时间卡顿的意思是，此GC启动之后，属于一次性完成所有操作, 于是单次 pause 的时间会较长。</p></blockquote><p>让我们看看并行垃圾收集器的GC日志长什么样, 从中我们可以得到哪些有用信息。下面的GC日志中显示了一次 minor GC 暂停 和一次 major GC 暂停:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">2022-05-09T14:27:40.915-0200: 116.115: [GC (Allocation Failure) </span><br><span class="line">        [PSYoungGen: 2694440K-&gt;1305132K(2796544K)] </span><br><span class="line">    9556775K-&gt;8438926K(11185152K)</span><br><span class="line">    , 0.2406675 secs] </span><br><span class="line">    [Times: user=1.77 sys=0.01, real=0.24 secs]</span><br><span class="line">2015-05-26T14:27:41.155-0200: 116.356: [Full GC (Ergonomics) </span><br><span class="line">        [PSYoungGen: 1305132K-&gt;0K(2796544K)] </span><br><span class="line">        [ParOldGen: 7133794K-&gt;6597672K(8388608K)] 8438926K-&gt;6597672K(11185152K), </span><br><span class="line">        [Metaspace: 6745K-&gt;6745K(1056768K)]</span><br><span class="line">    , 0.9158801 secs]</span><br><span class="line">    [Times: user=4.49 sys=0.64, real=0.92 secs]</span><br></pre></td></tr></table></figure><h3 id="Minor-GC-小型GC-1"><a href="#Minor-GC-小型GC-1" class="headerlink" title="Minor GC(小型GC)"></a>Minor GC(小型GC)</h3><p>第一次GC事件表示发生在年轻代的垃圾收集:</p><blockquote><p><code>2022-05-09T14:27:40.915-0200</code>1: <code>116.115</code>2: <code>[ GC</code>3 (<code>Allocation Failure</code>4)</p><p>[<code>PSYoungGen</code>5: <code>2694440K-&gt;1305132K</code>6 <code>(2796544K)</code>7] <code>9556775K-&gt;8438926K</code>8</p><p><code>(11185152K)</code>9, <code>0.2406675 secs</code>10]</p><p><code>[Times: user=1.77 sys=0.01, real=0.24 secs]</code>11</p><p>1、 <code>2022-05-09T14:27:40.915-0200</code> – GC事件开始的时间. 其中<code>-0200</code>表示西二时区,而中国所在的东8区为 <code>+0800</code>。</p><p>2、 <code>116.115</code> – GC事件开始时,相对于JVM启动时的间隔时间,单位是秒。</p><p>3、 <code>GC</code> – 用来区分 Minor GC 还是 Full GC 的标志。<code>GC</code>表明这是一次<strong>小型GC</strong>(Minor GC)</p><p>4、 <code>Allocation Failure</code> – 触发垃圾收集的原因。本次GC事件, 是由于年轻代中没有适当的空间存放新的数据结构引起的。</p><p>5、 <code>PSYoungGen</code> – 垃圾收集器的名称。这个名字表示的是在年轻代中使用的: 并行的 标记-复制(mark-copy), 全线暂停(STW) 垃圾收集器。</p><p>6、 <code>2694440K-&gt;1305132K</code> – 在垃圾收集之前和之后的年轻代使用量。</p><p>7、 <code>(2796544K)</code> – 年轻代的总大小。</p><p>8、 <code>9556775K-&gt;8438926K</code> – 在垃圾收集之前和之后整个堆内存的使用量。</p><p>9、 <code>(11185152K)</code> – 可用堆的总大小。</p><p>\10. <code>0.2406675 secs</code> – GC事件持续的时间,以秒为单位。</p><p>\11. <code>[Times: user=1.77 sys=0.01, real=0.24 secs]</code> – GC事件的持续时间, 通过三个部分来衡量:</p><ul><li>user – 在此次垃圾回收过程中, 由GC线程所消耗的总的CPU时间。</li><li>sys – GC过程中中操作系统调用和系统等待事件所消耗的时间。</li><li>real – 应用程序暂停的时间。在 Parallel GC 中, 这个数字约等于: (user time + system time)&#x2F;GC线程数。 这里使用了8个线程。 请注意,总有一定比例的处理过程是不能并行进行的。</li></ul></blockquote><p>所以,可以简单地算出, 在垃圾收集之前, 堆内存总使用量为 <strong>9,556,775K</strong>。 其中年轻代为 <strong>2,694,440K</strong>。同时算出老年代使用量为 <strong>6,862,335K</strong>. 在垃圾收集之后, 年轻代使用量减少为 <strong>1,389,308K</strong>, 但总的堆内存使用量只减少了 <code>1,117,849K</code>。这表示有大小为 <strong>271,459K</strong> 的对象从年轻代提升到老年代。</p><p><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://s3.uuu.ovh/imgs/2022/05/08/450768914559578a.png" alt="img_3.png"></p><h3 id="Full-GC-完全GC-1"><a href="#Full-GC-完全GC-1" class="headerlink" title="Full GC(完全GC)"></a>Full GC(完全GC)</h3><p>学习了并行GC如何清理年轻代之后, 下面介绍清理整个堆内存的GC日志以及如何进行分析:</p><blockquote><p><code>2022-05-09T14:27:41.155-0200</code> : <code>116.356</code> : [<code>Full GC</code> (<code>Ergonomics</code>)</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[PSYoungGen: 1305132K-&gt;0K(2796544K)]` [`ParOldGen` :`7133794K-&gt;6597672K</span><br></pre></td></tr></table></figure><p><code>(8388608K)</code>] <code>8438926K-&gt;6597672K</code> <code>(11185152K)</code>,</p><p><code>[Metaspace: 6745K-&gt;6745K(1056768K)]</code>, <code>0.9158801 secs</code>,</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[Times: user=4.49 sys=0.64, real=0.92 secs]</span><br></pre></td></tr></table></figure><p>1、 <code>2022-05-09T14:27:41.155-0200</code> – GC事件开始的时间. 其中<code>-0200</code>表示西二时区,而中国所在的东8区为 <code>+0800</code>。</p><p>2、 <code>116.356</code> – GC事件开始时,相对于JVM启动时的间隔时间,单位是秒。 我们可以看到, 此次事件在前一次 MinorGC完成之后立刻就开始了。</p><p>3、 <code>Full GC</code> – 用来表示此次是 Full GC 的标志。<code>Full GC</code>表明本次清理的是年轻代和老年代。</p><p>4、 <code>Ergonomics</code> – 触发垃圾收集的原因。<code>Ergonomics</code> 表示JVM内部环境认为此时可以进行一次垃圾收集。</p><p>5、 <code>[PSYoungGen: 1305132K-&gt;0K(2796544K)]</code> – 和上面的示例一样, 清理年轻代的垃圾收集器是名为 “PSYoungGen” 的STW收集器, 采用<strong>标记-复制</strong>(mark-copy)算法。 年轻代使用量从 <strong>1305132K</strong> 变为 <code>0</code>, 一般 Full GC 的结果都是这样。</p><p>6、 <code>ParOldGen</code> – 用于清理老年代空间的垃圾收集器类型。在这里使用的是名为 <strong>ParOldGen</strong> 的垃圾收集器, 这是一款并行 STW垃圾收集器, 算法为 标记-清除-整理(mark-sweep-compact)。</p><p>7、 <code>7133794K-&gt;6597672K</code> – 在垃圾收集之前和之后老年代内存的使用情况。</p><p>8、 <code>(8388608K)</code> – 老年代的总空间大小。</p><p>9、 <code>8438926K-&gt;6597672K</code> – 在垃圾收集之前和之后堆内存的使用情况。</p><p>\10. <code>(11185152K)</code> – 可用堆内存的总容量。</p><p>\11. <code>[Metaspace: 6745K-&gt;6745K(1056768K)]</code> – 类似的信息,关于 Metaspace 空间的。可以看出, 在GC事件中 Metaspace 里面没有回收任何对象。</p><p>\12. <code>0.9158801 secs</code> – GC事件持续的时间,以秒为单位。</p><p>\13. <code>[Times: user=4.49 sys=0.64, real=0.92 secs]</code> – GC事件的持续时间, 通过三个部分来衡量:</p><ul><li>user – 在此次垃圾回收过程中, 由GC线程所消耗的总的CPU时间。</li><li>sys – GC过程中中操作系统调用和系统等待事件所消耗的时间。</li><li>real – 应用程序暂停的时间。在 Parallel GC 中, 这个数字约等于: (user time + system time)&#x2F;GC线程数。 这里使用了8个线程。 请注意,总有一定比例的处理过程是不能并行进行的。</li></ul></blockquote><p>同样,和 Minor GC 的区别是很明显的 —— 在此次GC事件中, 除了年轻代, 还清理了老年代和 Metaspace. 在GC事件前后的内存布局如下图所示:</p><p><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://s3.uuu.ovh/imgs/2022/05/08/30abafa6fa110bc7.png" alt="img_4.png"></p><h2 id="Concurrent-Mark-and-Sweep-并发标记-清除"><a href="#Concurrent-Mark-and-Sweep-并发标记-清除" class="headerlink" title="Concurrent Mark and Sweep(并发标记-清除)"></a>Concurrent Mark and Sweep(并发标记-清除)</h2><p>CMS的官方名称为 “<strong>Mostly Concurrent Mark and Sweep Garbage Collector</strong>”(主要并发-标记-清除-垃圾收集器). 其对年轻代采用并行 STW方式的 <a target="_blank" rel="noopener external nofollow noreferrer" href="https://plumbr.eu/handbook/garbage-collection-algorithms/removing-unused-objects/copy">mark-copy (标记-复制)算法</a>, 对老年代主要使用并发 <a target="_blank" rel="noopener external nofollow noreferrer" href="https://plumbr.eu/handbook/garbage-collection-algorithms/removing-unused-objects/sweep">mark-sweep (标记-清除)算法</a>。</p><p>CMS的设计目标是避免在老年代垃圾收集时出现长时间的卡顿。主要通过两种手段来达成此目标。</p><ul><li>第一, 不对老年代进行整理, 而是使用空闲列表(free-lists)来管理内存空间的回收。</li><li>第二, 在 <strong>mark-and-sweep</strong> (标记-清除) 阶段的大部分工作和应用线程一起并发执行。</li></ul><p>也就是说, 在这些阶段并没有明显的应用线程暂停。但值得注意的是, 它仍然和应用线程争抢CPU时间。默认情况下, CMS 使用的并发线程数等于CPU内核数的 <code>1/4</code>。</p><p>通过以下选项来指定CMS垃圾收集器:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -XX:+UseConcMarkSweepGC com.mypackages.MyExecutableClass</span><br></pre></td></tr></table></figure><p>如果服务器是多核CPU，并且主要调优目标是降低延迟, 那么使用CMS是个很明智的选择. 减少每一次GC停顿的时间,会直接影响到终端用户对系统的体验, 用户会认为系统非常灵敏。 因为多数时候都有部分CPU资源被GC消耗, 所以在CPU资源受限的情况下,CMS会比并行GC的吞吐量差一些。</p><p>和前面的GC算法一样, 我们先来看看CMS算法在实际应用中的GC日志, 其中包括一次 minor GC, 以及一次 major GC 停顿:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">2022-05-09T16:23:07.219-0200: 64.322: [GC (Allocation Failure) 64.322: </span><br><span class="line">            [ParNew: 613404K-&gt;68068K(613440K), 0.1020465 secs]</span><br><span class="line">            10885349K-&gt;10880154K(12514816K), 0.1021309 secs]</span><br><span class="line">        [Times: user=0.78 sys=0.01, real=0.11 secs]</span><br><span class="line">2022-05-09T16:23:07.321-0200: 64.425: [GC (CMS Initial Mark) </span><br><span class="line">            [1 CMS-initial-mark: 10812086K(11901376K)] </span><br><span class="line">            10887844K(12514816K), 0.0001997 secs] </span><br><span class="line">        [Times: user=0.00 sys=0.00, real=0.00 secs]</span><br><span class="line">2022-05-09T16:23:07.321-0200: 64.425: [CMS-concurrent-mark-start]</span><br><span class="line">2022-05-09T16:23:07.357-0200: 64.460: [CMS-concurrent-mark: 0.035/0.035 secs] </span><br><span class="line">        [Times: user=0.07 sys=0.00, real=0.03 secs]</span><br><span class="line">2022-05-092015-05-26T16:23:07.357-0200: 64.460: [CMS-concurrent-preclean-start]</span><br><span class="line">2022-05-09T16:23:07.373-0200: 64.476: [CMS-concurrent-preclean: 0.016/0.016 secs] </span><br><span class="line">        [Times: user=0.02 sys=0.00, real=0.02 secs]</span><br><span class="line">2022-05-09T16:23:07.373-0200: 64.476: [CMS-concurrent-abortable-preclean-start]</span><br><span class="line">2022-05-09T16:23:08.446-0200: 65.550: [CMS-concurrent-abortable-preclean: 0.167/1.074 secs] </span><br><span class="line">        [Times: user=0.20 sys=0.00, real=1.07 secs]</span><br><span class="line">2022-05-09T16:23:08.447-0200: 65.550: [GC (CMS Final Remark) </span><br><span class="line">            [YG occupancy: 387920 K (613440 K)]</span><br><span class="line">            65.550: [Rescan (parallel) , 0.0085125 secs]</span><br><span class="line">            65.559: [weak refs processing, 0.0000243 secs]</span><br><span class="line">            65.559: [class unloading, 0.0013120 secs]</span><br><span class="line">            65.560: [scrub symbol table, 0.0008345 secs]</span><br><span class="line">            65.561: [scrub string table, 0.0001759 secs]</span><br><span class="line">            [1 CMS-remark: 10812086K(11901376K)] </span><br><span class="line">            11200006K(12514816K), 0.0110730 secs] </span><br><span class="line">        [Times: user=0.06 sys=0.00, real=0.01 secs]</span><br><span class="line">2022-05-09T16:23:08.458-0200: 65.561: [CMS-concurrent-sweep-start]</span><br><span class="line">2022-05-09T16:23:08.485-0200: 65.588: [CMS-concurrent-sweep: 0.027/0.027 secs] </span><br><span class="line">        [Times: user=0.03 sys=0.00, real=0.03 secs]</span><br><span class="line">2022-05-09T16:23:08.485-0200: 65.589: [CMS-concurrent-reset-start]</span><br><span class="line">2022-05-09T16:23:08.497-0200: 65.601: [CMS-concurrent-reset: 0.012/0.012 secs] </span><br><span class="line">        [Times: user=0.01 sys=0.00, real=0.01 secs]</span><br></pre></td></tr></table></figure><h3 id="Minor-GC-小型GC-2"><a href="#Minor-GC-小型GC-2" class="headerlink" title="Minor GC(小型GC)"></a>Minor GC(小型GC)</h3><p>日志中的第一次GC事件是清理年轻代的小型GC(Minor GC)。让我们来分析 CMS 垃圾收集器的行为:</p><blockquote><p><code>2022-05-09T16:23:07.219-0200</code>: <code>64.322</code>:[<code>GC</code>(<code>Allocation Failure</code>) 64.322:</p><p>[<code>ParNew</code>: <code>613404K-&gt;68068K``(613440K)</code>, <code>0.1020465 secs</code>]</p><p><code>10885349K-&gt;10880154K``(12514816K)</code>, <code>0.1021309 secs</code>]</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[Times: user=0.78 sys=0.01, real=0.11 secs]</span><br></pre></td></tr></table></figure><p>1、 <code>2022-05-09T16:23:07.219-0200</code> – GC事件开始的时间. 其中<code>-0200</code>表示西二时区,而中国所在的东8区为 <code>+0800</code>。</p><p>2、 <code>64.322</code> – GC事件开始时,相对于JVM启动时的间隔时间,单位是秒。</p><p>3、 <code>GC</code> – 用来区分 Minor GC 还是 Full GC 的标志。<code>GC</code>表明这是一次<strong>小型GC</strong>(Minor GC)</p><p>4、 <code>Allocation Failure</code> – 触发垃圾收集的原因。本次GC事件, 是由于年轻代中没有适当的空间存放新的数据结构引起的。</p><p>5、 <code>ParNew</code> – 垃圾收集器的名称。这个名字表示的是在年轻代中使用的: 并行的 标记-复制(mark-copy), 全线暂停(STW)垃圾收集器, 专门设计了用来配合老年代使用的 Concurrent Mark &amp; Sweep 垃圾收集器。</p><p>6、 <code>613404K-&gt;68068K</code> – 在垃圾收集之前和之后的年轻代使用量。</p><p>7、 <code>(613440K)</code> – 年轻代的总大小。</p><p>8、 <code>0.1020465 secs</code> – 垃圾收集器在 w&#x2F;o final cleanup 阶段消耗的时间</p><p>9、 <code>10885349K-&gt;10880154K</code> – 在垃圾收集之前和之后堆内存的使用情况。</p><p>\10. <code>(12514816K)</code> – 可用堆的总大小。</p><p>\11. <code>0.1021309 secs</code> – 垃圾收集器在标记和复制年轻代存活对象时所消耗的时间。包括和ConcurrentMarkSweep收集器的通信开销, 提升存活时间达标的对象到老年代,以及垃圾收集后期的一些最终清理。</p><p>\12. <code>[Times: user=0.78 sys=0.01, real=0.11 secs]</code> – GC事件的持续时间, 通过三个部分来衡量:</p><ul><li>user – 在此次垃圾回收过程中, 由GC线程所消耗的总的CPU时间。</li><li>sys – GC过程中中操作系统调用和系统等待事件所消耗的时间。</li><li>real – 应用程序暂停的时间。在并行GC(Parallel GC)中, 这个数字约等于: (user time + system time)&#x2F;GC线程数。 这里使用的是8个线程。 请注意,总是有固定比例的处理过程是不能并行化的。</li></ul></blockquote><p>从上面的日志可以看出,在GC之前总的堆内存使用量为 <strong>10,885,349K</strong>, 年轻代的使用量为 <strong>613,404K</strong>。这意味着老年代使用量等于 <strong>10,271,945K</strong>。GC之后,年轻代的使用量减少了 545,336K, 而总的堆内存使用只下降了 <strong>5,195K</strong>。可以算出有 <strong>540,141K</strong> 的对象从年轻代提升到老年代。</p><p><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://s3.uuu.ovh/imgs/2022/05/09/12071bf1bd0308fe.png" alt="img_5.png"></p><h3 id="Full-GC-完全GC-2"><a href="#Full-GC-完全GC-2" class="headerlink" title="Full GC(完全GC)"></a>Full GC(完全GC)</h3><p>现在, 我们已经熟悉了如何解读GC日志, 接下来将介绍一种完全不同的日志格式。下面这一段很长很长的日志, 就是CMS对老年代进行垃圾收集时输出的各阶段日志。为了简洁,我们对这些阶段逐个介绍。 首先来看CMS收集器整个GC事件的日志:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">2022-05-09T16:23:07.321-0200: 64.425: [GC (CMS Initial Mark) </span><br><span class="line">        [1 CMS-initial-mark: 10812086K(11901376K)] </span><br><span class="line">    10887844K(12514816K), 0.0001997 secs] </span><br><span class="line">    [Times: user=0.00 sys=0.00, real=0.00 secs]</span><br><span class="line">2022-05-09T16:23:07.321-0200: 64.425: [CMS-concurrent-mark-start]</span><br><span class="line">2022-05-09T16:23:07.357-0200: 64.460: [CMS-concurrent-mark: 0.035/0.035 secs] </span><br><span class="line">    [Times: user=0.07 sys=0.00, real=0.03 secs]</span><br><span class="line">2022-05-09T16:23:07.357-0200: 64.460: [CMS-concurrent-preclean-start]</span><br><span class="line">2022-05-09T16:23:07.373-0200: 64.476: [CMS-concurrent-preclean: 0.016/0.016 secs] </span><br><span class="line">    [Times: user=0.02 sys=0.00, real=0.02 secs]</span><br><span class="line">2022-05-09T16:23:07.373-0200: 64.476: [CMS-concurrent-abortable-preclean-start]</span><br><span class="line">2022-05-09T16:23:08.446-0200: 65.550: [CMS-concurrent-abortable-preclean: 0.167/1.074 secs] </span><br><span class="line">    [Times: user=0.20 sys=0.00, real=1.07 secs]</span><br><span class="line">2022-05-09T16:23:08.447-0200: 65.550: [GC (CMS Final Remark) </span><br><span class="line">        [YG occupancy: 387920 K (613440 K)]</span><br><span class="line">        65.550: [Rescan (parallel) , 0.0085125 secs]</span><br><span class="line">        65.559: [weak refs processing, 0.0000243 secs]</span><br><span class="line">        65.559: [class unloading, 0.0013120 secs]</span><br><span class="line">        65.560: [scrub symbol table, 0.0008345 secs]</span><br><span class="line">        65.561: [scrub string table, 0.0001759 secs]</span><br><span class="line">        [1 CMS-remark: 10812086K(11901376K)] </span><br><span class="line">    11200006K(12514816K), 0.0110730 secs] </span><br><span class="line">    [Times: user=0.06 sys=0.00, real=0.01 secs]</span><br><span class="line">2022-05-09T16:23:08.458-0200: 65.561: [CMS-concurrent-sweep-start]</span><br><span class="line">2022-05-09T16:23:08.485-0200: 65.588: [CMS-concurrent-sweep: 0.027/0.027 secs] </span><br><span class="line">    [Times: user=0.03 sys=0.00, real=0.03 secs]</span><br><span class="line">2022-05-09T16:23:08.485-0200: 65.589: [CMS-concurrent-reset-start]</span><br><span class="line">2022-05-09T16:23:08.497-0200: 65.601: [CMS-concurrent-reset: 0.012/0.012 secs] </span><br><span class="line">    [Times: user=0.01 sys=0.00, real=0.01 secs]</span><br></pre></td></tr></table></figure><p>只是要记住 —— 在实际情况下, 进行老年代的并发回收时, 可能会伴随着多次年轻代的小型GC. 在这种情况下, 大型GC的日志中就会掺杂着多次小型GC事件, 像前面所介绍的一样。</p><p><strong>阶段 1: Initial Mark(初始标记)</strong>. 这是第一次STW事件。 此阶段的目标是标记老年代中所有存活的对象, 包括 GC ROOR 的直接引用, 以及由年轻代中存活对象所引用的对象。 后者也非常重要, 因为老年代是独立进行回收的。</p><p><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://s3.uuu.ovh/imgs/2022/05/09/e9de9390200c3085.png" alt="img_6.png"></p><blockquote><p><code>2022-05-09T16:23:07.321-0200: 64.42</code>1: [GC (<code>CMS Initial Mark</code>1</p><p>[1 CMS-initial-mark: <code>10812086K</code>1<code>(11901376K)</code>1] <code>10887844K</code>1<code>(12514816K)</code>1,</p><p><code>0.0001997 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]</code>1</p><p>1、 <code>2022-05-09T16:23:07.321-0200: 64.42</code> – GC事件开始的时间. 其中 <code>-0200</code> 是时区,而中国所在的东8区为 +0800。 而 <strong>64.42</strong> 是相对于JVM启动的时间。 下面的其他阶段也是一样,所以就不再重复介绍。</p><p>2、 <code>CMS Initial Mark</code> – 垃圾回收的阶段名称为 “Initial Mark”。 标记所有的 GC Root。</p><p>3、 <code>10812086K</code> – 老年代的当前使用量。</p><p>4、 <code>(11901376K)</code> – 老年代中可用内存总量。</p><p>5、 <code>10887844K</code> – 当前堆内存的使用量。</p><p>6、 <code>(12514816K)</code> – 可用堆的总大小。</p><p>7、 <code>0.0001997 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]</code> – 此次暂停的持续时间, 以 user, system 和 real time 3个部分进行衡量。</p></blockquote><p><strong>阶段 2: Concurrent Mark(并发标记).</strong> 在此阶段, 垃圾收集器遍历老年代, 标记所有的存活对象, 从前一阶段 “Initial Mark” 找到的 root 根开始算起。 顾名思义, “并发标记”阶段, 就是与应用程序同时运行,不用暂停的阶段。 请注意, 并非所有老年代中存活的对象都在此阶段被标记, 因为在标记过程中对象的引用关系还在发生变化。</p><p><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://s3.uuu.ovh/imgs/2022/05/09/10dce946b0e8db67.png" alt="img_7.png"></p><p>在上面的示意图中, “Current object” 旁边的一个引用被标记线程并发删除了。</p><blockquote><p>2022-05-09T16:23:07.321-0200: 64.425: [CMS-concurrent-mark-start]</p><p>2022-05-09T16:23:07.357-0200: 64.460: [<code>CMS-concurrent-mark</code>1: <code>035/0.035 secs</code>1]</p><p><code>[Times: user=0.07 sys=0.00, real=0.03 secs]</code>1</p><p>1、<code>CMS-concurrent-mark</code> – 并发标记(“Concurrent Mark”) 是CMS垃圾收集中的一个阶段, 遍历老年代并标记所有的存活对象。</p><p>2、<code>035/0.035 secs</code> – 此阶段的持续时间, 分别是运行时间和相应的实际时间。</p><p>3、<code>[Times: user=0.07 sys=0.00, real=0.03 secs]</code> – <code>Times</code> 这部分对并发阶段来说没多少意义, 因为是从并发标记开始时计算的,而这段时间内不仅并发标记在运行,程序也在运行</p></blockquote><p><strong>阶段 3: Concurrent Preclean(并发预清理).</strong> 此阶段同样是与应用线程并行执行的, 不需要停止应用线程。 因为前一阶段是与程序并发进行的,可能有一些引用已经改变。如果在并发标记过程中发生了引用关系变化,JVM会(通过“Card”)将发生了改变的区域标记为“脏”区(这就是所谓的<a target="_blank" rel="noopener external nofollow noreferrer" href="http://psy-lob-saw.blogspot.com.ee/2014/10/the-jvm-write-barrier-card-marking.html">卡片标记,Card Marking</a>)。</p><p><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://s3.uuu.ovh/imgs/2022/05/09/5f7c40896eb7ab24.png" alt="img_8.png"></p><p>在预清理阶段,这些脏对象会被统计出来,从他们可达的对象也被标记下来。此阶段完成后, 用以标记的 card 也就被清空了。</p><p><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://s3.uuu.ovh/imgs/2022/05/09/da00c81473db72b0.png" alt="img_9.png"></p><p>此外, 本阶段也会执行一些必要的细节处理, 并为 Final Remark 阶段做一些准备工作。</p><blockquote><p>2022-05-09T16:23:07.357-0200: 64.460: [CMS-concurrent-preclean-start]</p><p>2022-05-09T16:23:07.373-0200: 64.476: [<code>CMS-concurrent-preclean</code>: <code>0.016/0.016 secs</code>] <code>[Times: user=0.02 sys=0.00, real=0.02 secs]</code></p><p>1、<code>CMS-concurrent-preclean</code> – 并发预清理阶段, 统计此前的标记阶段中发生了改变的对象。</p><p>2、<code>0.016/0.016 secs</code> – 此阶段的持续时间, 分别是运行时间和对应的实际时间。</p><p>3、<code>[Times: user=0.02 sys=0.00, real=0.02 secs]</code> – Times 这部分对并发阶段来说没多少意义, 因为是从并发标记开始时计算的,而这段时间内不仅GC的并发标记在运行,程序也在运行。</p></blockquote><p><strong>阶段 4: Concurrent Abortable Preclean(并发可取消的预清理).</strong> 此阶段也不停止应用线程. 本阶段尝试在 STW 的 Final Remark 之前尽可能地多做一些工作。本阶段的具体时间取决于多种因素, 因为它循环做同样的事情,直到满足某个退出条件( 如迭代次数, 有用工作量, 消耗的系统时间,等等)。</p><blockquote><p>2022-05-09T16:23:07.373-0200: 64.476: [CMS-concurrent-abortable-preclean-start]</p><p>2022-05-09T16:23:08.446-0200: 65.550: [CMS-concurrent-abortable-preclean1: 0.167&#x2F;1.074 secs2][Times: user&#x3D;0.20 sys&#x3D;0.00, real&#x3D;1.07 secs]3</p><p>1、 <code>CMS-concurrent-abortable-preclean</code> – 此阶段的名称: “Concurrent Abortable Preclean”。</p><p>2、 <code>0.167/1.074 secs</code> – 此阶段的持续时间, 运行时间和对应的实际时间。有趣的是, 用户时间明显比时钟时间要小很多。通常情况下我们看到的都是时钟时间小于用户时间, 这意味着因为有一些并行工作, 所以运行时间才会小于使用的CPU时间。这里只进行了少量的工作 — 0.167秒的CPU时间,GC线程经历了很多系统等待。从本质上讲,GC线程试图在必须执行 STW暂停之前等待尽可能长的时间。默认条件下,此阶段可以持续最多5秒钟。</p><p>3、 <code>[Times: user=0.20 sys=0.00, real=1.07 secs]</code> – “Times” 这部分对并发阶段来说没多少意义, 因为是从并发标记开始时计算的,而这段时间内不仅GC的并发标记线程在运行,程序也在运行</p></blockquote><p>此阶段可能显著影响STW停顿的持续时间, 并且有许多重要的<a target="_blank" rel="noopener external nofollow noreferrer" href="https://blogs.oracle.com/jonthecollector/entry/did_you_know">配置选项</a>和失败模式。</p><p><strong>阶段 5: Final Remark(最终标记).</strong> 这是此次GC事件中第二次(也是最后一次)STW阶段。本阶段的目标是完成老年代中所有存活对象的标记. 因为之前的 preclean 阶段是并发的, 有可能无法跟上应用程序的变化速度。所以需要 STW暂停来处理复杂情况。</p><p>通常CMS会尝试在年轻代尽可能空的情况运行 final remark 阶段, 以免接连多次发生 STW 事件。</p><p>看起来稍微比之前的阶段要复杂一些:</p><blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">2022-05-09T16:23:08.447-0200: 65.550`: [GC (`CMS Final Remark`) [`YG occupancy: 387920 K (613440 K)`]</span><br><span class="line">65.550: `[Rescan (parallel) , 0.0085125 secs]` 65.559: [`weak refs processing, 0.0000243 secs]65.559`: [`class unloading, 0.0013120 secs]65.560`: [`scrub string table, 0.0001759 secs`]</span><br><span class="line">[1 CMS-remark: `10812086K(11901376K)`] `11200006K(12514816K)`,`0.0110730 secs`] `[Times: user=0.06 sys=0.00, real=0.01 secs]</span><br></pre></td></tr></table></figure><p>1、 <code>2022-05-09T16:23:08.447-0200: 65.550</code> – GC事件开始的时间. 包括时钟时间,以及相对于JVM启动的时间. 其中<code>-0200</code>表示西二时区,而中国所在的东8区为 <code>+0800</code>。</p><p>2、 <code>CMS Final Remark</code> – 此阶段的名称为 “Final Remark”, 标记老年代中所有存活的对象，包括在此前的并发标记过程中创建&#x2F;修改的引用。</p><p>3、 <code>YG occupancy: 387920 K (613440 K)</code> – 当前年轻代的使用量和总容量。</p><p>4、 <code>[Rescan (parallel) , 0.0085125 secs]</code> – 在程序暂停时重新进行扫描(Rescan),以完成存活对象的标记。此时 rescan 是并行执行的,消耗的时间为 <strong>0.0085125秒</strong>。</p><p>5、 <code>weak refs processing, 0.0000243 secs]65.559</code> – 处理弱引用的第一个子阶段(sub-phases)。 显示的是持续时间和开始时间戳。</p><p>6、 <code>class unloading, 0.0013120 secs]65.560</code> – 第二个子阶段, 卸载不使用的类。 显示的是持续时间和开始的时间戳。</p><p>7、 <code>scrub string table, 0.0001759 secs</code> – 最后一个子阶段, 清理持有class级别 metadata 的符号表(symbol tables),以及内部化字符串对应的 string tables。当然也显示了暂停的时钟时间。</p><p>8、 <code>10812086K(11901376K)</code> – 此阶段完成后老年代的使用量和总容量</p><p>9、 <code>11200006K(12514816K)</code> – 此阶段完成后整个堆内存的使用量和总容量</p><p>\10. <code>0.0110730 secs</code> – 此阶段的持续时间。</p><p>\11. <code>[Times: user=0.06 sys=0.00, real=0.01 secs]</code> – GC事件的持续时间, 通过不同的类别来衡量: user, system and real time。</p></blockquote><p>在5个标记阶段完成之后, 老年代中所有的存活对象都被标记了, 现在GC将清除所有不使用的对象来回收老年代空间:</p><p><strong>阶段 6: Concurrent Sweep(并发清除).</strong> 此阶段与应用程序并发执行,不需要STW停顿。目的是删除未使用的对象,并收回他们占用的空间。</p><p><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://s3.uuu.ovh/imgs/2022/05/09/c9a6eae4c817864b.png" alt="img_10.png"></p><blockquote><p>2022-05-09T16:23:08.458-0200: 65.561: [CMS-concurrent-sweep-start] 2015-05-26T16:23:08.485-0200: 65.588: [<code>CMS-concurrent-sweep</code>: <code>0.027/0.027 secs</code>] <code>[Times: user=0.03 sys=0.00, real=0.03 secs]</code></p><p>1、 <code>CMS-concurrent-sweep</code> – 此阶段的名称, “Concurrent Sweep”, 清除未被标记、不再使用的对象以释放内存空间。</p><p>2、 <code>0.027/0.027 secs</code> – 此阶段的持续时间, 分别是运行时间和实际时间</p><p>3、 <code>[Times: user=0.03 sys=0.00, real=0.03 secs]</code> – “Times”部分对并发阶段来说没有多少意义, 因为是从并发标记开始时计算的,而这段时间内不仅是并发标记在运行,程序也在运行。</p></blockquote><p><strong>阶段 7: Concurrent Reset(并发重置).</strong> 此阶段与应用程序并发执行,重置CMS算法相关的内部数据, 为下一次GC循环做准备。</p><blockquote><p>2022-05-09T16:23:08.485-0200: 65.589: [CMS-concurrent-reset-start] 2015-05-26T16:23:08.497-0200: 65.601: [<code>CMS-concurrent-reset</code>: <code>0.012/0.012 secs</code>] <code>[Times: user=0.01 sys=0.00, real=0.01 secs]</code></p><p>1、 <code>CMS-concurrent-reset</code> – 此阶段的名称, “Concurrent Reset”, 重置CMS算法的内部数据结构, 为下一次GC循环做准备。</p><p>2、 <code>0.012/0.012 secs</code> – 此阶段的持续时间, 分别是运行时间和对应的实际时间</p><p>3、 <code>[Times: user=0.01 sys=0.00, real=0.01 secs]</code> – “Times”部分对并发阶段来说没多少意义, 因为是从并发标记开始时计算的,而这段时间内不仅GC线程在运行,程序也在运行。</p></blockquote><p>总之, CMS垃圾收集器在减少停顿时间上做了很多给力的工作, 大量的并发线程执行的工作并不需要暂停应用线程。 当然, CMS也有一些缺点,其中最大的问题就是老年代内存碎片问题, 在某些情况下GC会造成不可预测的暂停时间, 特别是堆内存较大的情况下。</p><h2 id="G1-–-Garbage-First-垃圾优先算法"><a href="#G1-–-Garbage-First-垃圾优先算法" class="headerlink" title="G1 – Garbage First(垃圾优先算法)"></a>G1 – Garbage First(垃圾优先算法)</h2><p>G1最主要的设计目标是: 将STW停顿的时间和分布变成可预期以及可配置的。事实上, G1是一款软实时垃圾收集器, 也就是说可以为其设置某项特定的性能指标. 可以指定: 在任意 <code>xx</code> 毫秒的时间范围内, STW停顿不得超过 <code>x</code> 毫秒。 如: 任意1秒暂停时间不得超过5毫秒. Garbage-First GC 会尽力达成这个目标(有很大的概率会满足, 但并不完全确定,具体是多少将是硬实时的[hard real-time])。</p><p>为了达成这项指标, G1 有一些独特的实现。首先, 堆不再分成连续的年轻代和老年代空间。而是划分为多个(通常是2048个)可以存放对象的 **小堆区(smaller heap regions)**。每个小堆区都可能是 Eden区, Survivor区或者Old区. 在逻辑上, 所有的Eden区和Survivor区合起来就是年轻代, 所有的Old区拼在一起那就是老年代:</p><p><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://s3.uuu.ovh/imgs/2022/05/09/ac01f621f8228fc1.png" alt="img_11.png"></p><p>这样的划分使得 GC不必每次都去收集整个堆空间, 而是以增量的方式来处理: 每次只处理一部分小堆区,称为此次的回收集(collection set). 每次暂停都会收集所有年轻代的小堆区, 但可能只包含一部分老年代小堆区:</p><p><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://s3.uuu.ovh/imgs/2022/05/09/37c3112823842048.png" alt="img_12.png"></p><p>G1的另一项创新, 是在并发阶段估算每个小堆区存活对象的总数。用来构建回收集(collection set)的原则是: <strong>垃圾最多的小堆区会被优先收集</strong>。这也是G1名称的由来: garbage-first。</p><p>要启用G1收集器, 使用的命令行参数为:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -XX:+UseG1GC com.mypackages.MyExecutableClass</span><br></pre></td></tr></table></figure><h3 id="Evacuation-Pause-Fully-Young-转移暂停-纯年轻代模式"><a href="#Evacuation-Pause-Fully-Young-转移暂停-纯年轻代模式" class="headerlink" title="Evacuation Pause: Fully Young(转移暂停:纯年轻代模式)"></a>Evacuation Pause: Fully Young(转移暂停:纯年轻代模式)</h3><p>在应用程序刚启动时, G1还未执行过(not-yet-executed)并发阶段, 也就没有获得任何额外的信息, 处于初始的 fully-young 模式. 在年轻代空间用满之后, 应用线程被暂停, 年轻代堆区中的存活对象被复制到存活区, 如果还没有存活区,则选择任意一部分空闲的小堆区用作存活区。</p><p>复制的过程称为转移(Evacuation), 这和前面讲过的年轻代收集器基本上是一样的工作原理。转移暂停的日志信息很长,为简单起见, 我们去除了一些不重要的信息. 在并发阶段之后我们会进行详细的讲解。此外, 由于日志记录很多, 所以并行阶段和“其他”阶段的日志将拆分为多个部分来进行讲解:</p><blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">0.134: [GC pause (G1 Evacuation Pause) (young), 0.0144119 secs]</span><br><span class="line">[Parallel Time: 13.9 ms, GC Workers: 8]</span><br><span class="line">…</span><br><span class="line">[Code Root Fixup: 0.0 ms]</span><br><span class="line">[Code Root Purge: 0.0 ms]</span><br></pre></td></tr></table></figure><p>[Clear CT: 0.1 ms]</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[Other: 0.4 ms]`</span><br><span class="line">`…</span><br><span class="line">[Eden: 24.0M(24.0M)-&gt;0.0B(13.0M)` `Survivors: 0.0B-&gt;3072.0K` `Heap: 24.0M(256.0M)-&gt;21.9M(256.0M)]`</span><br><span class="line">`[Times: user=0.04 sys=0.04, real=0.02 secs]</span><br></pre></td></tr></table></figure><p>1、 <code>0.134: [GC pause (G1 Evacuation Pause) (young), 0.0144119 secs]</code> – G1转移暂停,只清理年轻代空间。暂停在JVM启动之后 134 ms 开始, 持续的系统时间为 <strong>0.0144秒</strong> 。</p><p>2、 <code>[Parallel Time: 13.9 ms, GC Workers: 8]</code> – 表明后面的活动由8个 Worker 线程并行执行, 消耗时间为13.9毫秒(real time)。</p><p>3、 <code>…</code> – 为阅读方便, 省略了部分内容,请参考后文。</p><p>4、 <code>[Code Root Fixup: 0.0 ms]</code> – 释放用于管理并行活动的内部数据。一般都接近于零。这是串行执行的过程。</p><p>5、 <code>[Code Root Purge: 0.0 ms]</code> – 清理其他部分数据, 也是非常快的, 但如非必要则几乎等于零。这是串行执行的过程。</p><p>6、 <code>[Other: 0.4 ms]</code> – 其他活动消耗的时间, 其中有很多是并行执行的。</p><p>7、 <code>…</code> – 请参考后文。</p><p>8、 <code>[Eden: 24.0M(24.0M)-&gt;0.0B(13.0M)</code> – 暂停之前和暂停之后, Eden 区的使用量&#x2F;总容量。</p><p>9、 <code>Survivors: 0.0B-&gt;3072.0K</code> – 暂停之前和暂停之后, 存活区的使用量。</p><p>\10. <code>Heap: 24.0M(256.0M)-&gt;21.9M(256.0M)]</code> – 暂停之前和暂停之后, 整个堆内存的使用量与总容量。</p><p>\11. <code>[Times: user=0.04 sys=0.04, real=0.02 secs]</code> – GC事件的持续时间, 通过三个部分来衡量:</p><p>* user – 在此次垃圾回收过程中, 由GC线程所消耗的总的CPU时间。<br>* sys – GC过程中, 系统调用和系统等待事件所消耗的时间。<br>* real – 应用程序暂停的时间。在并行GC(Parallel GC)中, 这个数字约等于: (user time + system time)&#x2F;GC线程数。 这里使用的是8个线程。 请注意,总是有一定比例的处理过程是不能并行化的。</p></blockquote><p>说明: 系统时间(wall clock time, elapsed time), 是指一段程序从运行到终止，系统时钟走过的时间。一般来说，系统时间都是要大于CPU时间</p><p>最繁重的GC任务由多个专用的 worker 线程来执行。下面的日志描述了他们的行为:</p><blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[Parallel Time: 13.9 ms, GC Workers: 8]</span><br></pre></td></tr></table></figure><p><code>[GC Worker Start (ms)</code>: Min: 134.0, Avg: 134.1, Max: 134.1, Diff: 0.1]</p><p><code>[Ext Root Scanning (ms)</code>: Min: 0.1, Avg: 0.2, Max: 0.3, Diff: 0.2, Sum: 1.2]<br>[Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]</p><p>[Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]</p><p>[Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]</p><p><code>[Code Root Scanning (ms)</code>: Min: 0.0, Avg: 0.0, Max: 0.2, Diff: 0.2, Sum: 0.2]</p><p><code>[Object Copy (ms)</code>: Min: 10.8, Avg: 12.1, Max: 12.6, Diff: 1.9, Sum: 96.5]</p><p><code>[Termination (ms)</code>: Min: 0.8, Avg: 1.5, Max: 2.8, Diff: 1.9, Sum: 12.2]</p><p><code>[Termination Attempts</code>: Min: 173, Avg: 293.2, Max: 362, Diff: 189, Sum: 2346]</p><p><code>[GC Worker Other (ms)</code>: Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]</p><p><code>GC Worker Total (ms)</code>: Min: 13.7, Avg: 13.8, Max: 13.8, Diff: 0.1, Sum: 110.2]</p><p><code>[GC Worker End (ms)</code>: Min: 147.8, Avg: 147.8, Max: 147.8, Diff: 0.0]</p><p>1、<code>[Parallel Time: 13.9 ms, GC Workers: 8]</code> – 表明下列活动由8个线程并行执行,消耗的时间为13.9毫秒(real time)。</p><p>2、<code>[GC Worker Start (ms)</code> – GC的worker线程开始启动时,相对于 pause 开始的时间戳。如果 <code>Min</code> 和 <code>Max</code> 差别很大,则表明本机其他进程所使用的线程数量过多, 挤占了GC的CPU时间。</p><p>3、<code>[Ext Root Scanning (ms)</code> – 用了多长时间来扫描堆外(non-heap)的root, 如 classloaders, JNI引用, JVM的系统root等。后面显示了运行时间, “Sum” 指的是CPU时间。</p><p>4、<code>[Code Root Scanning (ms)</code> – 用了多长时间来扫描实际代码中的 root: 例如局部变量等等(local vars)。</p><p>5、<code>[Object Copy (ms)</code> – 用了多长时间来拷贝收集区内的存活对象。</p><p>6、<code>[Termination (ms)</code> – GC的worker线程用了多长时间来确保自身可以安全地停止, 这段时间什么也不用做, stop 之后该线程就终止运行了。</p><p>7、<code>[Termination Attempts</code> – GC的worker 线程尝试多少次 try 和 teminate。如果worker发现还有一些任务没处理完,则这一次尝试就是失败的, 暂时还不能终止。</p><p>8、<code>[GC Worker Other (ms)</code> – 一些琐碎的小活动,在GC日志中不值得单独列出来。</p><p>9、<code>GC Worker Total (ms)</code> – GC的worker 线程的工作时间总计。</p><p>10、<code>[GC Worker End (ms)</code> – GC的worker 线程完成作业的时间戳。通常来说这部分数字应该大致相等, 否则就说明有太多的线程被挂起, 很可能是因为[坏邻居效应(noisy neighbor)](<a target="_blank" rel="noopener external nofollow noreferrer" href="https://github.com/cncounter/translation/blob/master/tiemao_2016/45_noisy_neighbors/noisy_neighbor_cloud">https://github.com/cncounter/translation/blob/master/tiemao_2016/45_noisy_neighbors/noisy_neighbor_cloud</a> _performance.md) 所导致的。</p></blockquote><p>此外,在转移暂停期间,还有一些琐碎执行的小活动。这里我们只介绍其中的一部分, 其余的会在后面进行讨论。</p><blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[Other: 0.4 ms]</span><br></pre></td></tr></table></figure><p>[Choose CSet: 0.0 ms]</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[Ref Proc: 0.2 ms]</span><br><span class="line">[Ref Enq: 0.0 ms]</span><br></pre></td></tr></table></figure><p>[Redirty Cards: 0.1 ms]</p><p>[Humongous Register: 0.0 ms]</p><p>[Humongous Reclaim: 0.0 ms]</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[Free CSet: 0.0 ms]</span><br></pre></td></tr></table></figure><p>1、 <code>[Other: 0.4 ms]</code> – 其他活动消耗的时间, 其中有很多也是并行执行的。</p><p>2、 <code>[Ref Proc: 0.2 ms]</code> – 处理非强引用(non-strong)的时间: 进行清理或者决定是否需要清理。</p><p>3、 <code>[Ref Enq: 0.0 ms]</code> – 用来将剩下的 non-strong 引用排列到合适的 <strong>ReferenceQueue</strong>中。</p><p>4、 <code>[Free CSet: 0.0 ms]</code> – 将回收集中被释放的小堆归还所消耗的时间, 以便他们能用来分配新的对象。</p></blockquote><h3 id="Concurrent-Marking-并发标记"><a href="#Concurrent-Marking-并发标记" class="headerlink" title="Concurrent Marking(并发标记)"></a>Concurrent Marking(并发标记)</h3><p>G1收集器的很多概念建立在CMS的基础上,所以下面的内容需要你对CMS有一定的理解. 虽然也有很多地方不同, 但并发标记的目标基本上是一样的. G1的并发标记通过 <strong>Snapshot-At-The-Beginning(开始时快照)</strong> 的方式, 在标记阶段开始时记下所有的存活对象。即使在标记的同时又有一些变成了垃圾. 通过对象是存活信息, 可以构建出每个小堆区的存活状态, 以便回收集能高效地进行选择。</p><p>这些信息在接下来的阶段会用来执行老年代区域的垃圾收集。在两种情况下是完全地并发执行的： 一、如果在标记阶段确定某个小堆区只包含垃圾; 二、在STW转移暂停期间, 同时包含垃圾和存活对象的老年代小堆区。</p><p>当堆内存的总体使用比例达到一定数值时,就会触发并发标记。默认值为 <code>45%</code>, 但也可以通过JVM参数 <strong><code>InitiatingHeapOccupancyPercent</code></strong> 来设置。和CMS一样, G1的并发标记也是由多个阶段组成, 其中一些是完全并发的, 还有一些阶段需要暂停应用线程。</p><p><strong>阶段 1: Initial Mark(初始标记)。</strong> 此阶段标记所有从GC root 直接可达的对象。在CMS中需要一次STW暂停, 但G1里面通常是在转移暂停的同时处理这些事情, 所以它的开销是很小的. 可以在 Evacuation Pause 日志中的第一行看到(initial-mark)暂停:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">1.631: [GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0062656 secs]</span><br></pre></td></tr></table></figure><p><strong>阶段 2: Root Region Scan(Root区扫描).</strong> 此阶段标记所有从 “根区域” 可达的存活对象。 根区域包括: 非空的区域, 以及在标记过程中不得不收集的区域。因为在并发标记的过程中迁移对象会造成很多麻烦, 所以此阶段必须在下一次转移暂停之前完成。如果必须启动转移暂停, 则会先要求根区域扫描中止, 等它完成才能继续扫描. 在当前版本的实现中, 根区域是存活的小堆区: y包括下一次转移暂停中肯定会被清理的那部分年轻代小堆区。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">1.362: [GC concurrent-root-region-scan-start]</span><br><span class="line">1.364: [GC concurrent-root-region-scan-end, 0.0028513 secs]</span><br></pre></td></tr></table></figure><p><strong>阶段 3: Concurrent Mark(并发标记).</strong> 此阶段非常类似于CMS: 它只是遍历对象图, 并在一个特殊的位图中标记能访问到的对象. 为了确保标记开始时的快照准确性, 所有应用线程并发对对象图执行的引用更新,G1 要求放弃前面阶段为了标记目的而引用的过时引用。</p><p>这是通过使用 <code>Pre-Write</code> 屏障来实现的,(不要和之后介绍的 <code>Post-Write</code> 混淆, 也不要和多线程开发中的内存屏障(memory barriers)相混淆)。Pre-Write屏障的作用是: G1在进行并发标记时, 如果程序将对象的某个属性做了变更, 就会在 log buffers 中存储之前的引用。 由并发标记线程负责处理。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">1.364: [GC concurrent-mark-start]</span><br><span class="line">1.645: [GC co ncurrent-mark-end, 0.2803470 secs]</span><br></pre></td></tr></table></figure><p><strong>阶段 4: Remark(再次标记).</strong> 和CMS类似,这也是一次STW停顿,以完成标记过程。对于G1,它短暂地停止应用线程, 停止并发更新日志的写入, 处理其中的少量信息, 并标记所有在并发标记开始时未被标记的存活对象。这一阶段也执行某些额外的清理, 如引用处理(参见 Evacuation Pause log) 或者类卸载(class unloading)。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">1.645: [GC remark 1.645: [Finalize Marking, 0.0009461 secs]</span><br><span class="line">1.646: [GC ref-proc, 0.0000417 secs] 1.646: </span><br><span class="line">    [Unloading, 0.0011301 secs], 0.0074056 secs]</span><br><span class="line">[Times: user=0.01 sys=0.00, real=0.01 secs]</span><br></pre></td></tr></table></figure><p><strong>阶段 5: Cleanup(清理).</strong> 最后这个小阶段为即将到来的转移阶段做准备, 统计小堆区中所有存活的对象, 并将小堆区进行排序, 以提升GC的效率. 此阶段也为下一次标记执行所有必需的整理工作(house-keeping activities): 维护并发标记的内部状态。</p><p>最后要提醒的是, 所有不包含存活对象的小堆区在此阶段都被回收了。有一部分是并发的: 例如空堆区的回收,还有大部分的存活率计算, 此阶段也需要一个短暂的STW暂停, 以不受应用线程的影响来完成作业. 这种STW停顿的日志如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">1.652: [GC cleanup 1213M-&gt;1213M(1885M), 0.0030492 secs]</span><br><span class="line">[Times: user=0.01 sys=0.00, real=0.00 secs]</span><br></pre></td></tr></table></figure><p>如果发现某些小堆区中只包含垃圾, 则日志格式可能会有点不同, 如:</p><p>1、872: [GC cleanup 1357M-&gt;173M(1996M), 0.0015664 secs]<br>[Times: user&#x3D;0.01 sys&#x3D;0.00, real&#x3D;0.01 secs]<br>1、874: [GC concurrent-cleanup-start]<br>1、876: [GC concurrent-cleanup-end, 0.0014846 secs]</p><h3 id="Evacuation-Pause-Mixed-转移暂停-混合模式"><a href="#Evacuation-Pause-Mixed-转移暂停-混合模式" class="headerlink" title="Evacuation Pause: Mixed (转移暂停: 混合模式)"></a>Evacuation Pause: Mixed (转移暂停: 混合模式)</h3><p>能并发清理老年代中整个整个的小堆区是一种最优情形, 但有时候并不是这样。并发标记完成之后, G1将执行一次混合收集(mixed collection), 不只清理年轻代, 还将一部分老年代区域也加入到 collection set 中。</p><p>混合模式的转移暂停(Evacuation pause)不一定紧跟着并发标记阶段。有很多规则和历史数据会影响混合模式的启动时机。比如, 假若在老年代中可以并发地腾出很多的小堆区,就没有必要启动混合模式。</p><p>因此, 在并发标记与混合转移暂停之间, 很可能会存在多次 fully-young 转移暂停。</p><p>添加到回收集的老年代小堆区的具体数字及其顺序, 也是基于许多规则来判定的。 其中包括指定的软实时性能指标, 存活性,以及在并发标记期间收集的GC效率等数据, 外加一些可配置的JVM选项. 混合收集的过程, 很大程度上和前面的 fully-young gc 是一样的, 但这里我们还要介绍一个概念: remembered sets(历史记忆集)。</p><p>Remembered sets (历史记忆集)是用来支持不同的小堆区进行独立回收的。例如,在收集A、B、C区时, 我们必须要知道是否有从D区或者E区指向其中的引用, 以确定他们的存活性. 但是遍历整个堆需要相当长的时间, 这就违背了增量收集的初衷, 因此必须采取某种优化手段. 其他GC算法有独立的 Card Table 来支持年轻代的垃圾收集一样, 而G1中使用的是 Remembered Sets。</p><p>如下图所示, 每个小堆区都有一个 <strong>remembered set</strong>, 列出了从外部指向本区的所有引用。这些引用将被视为附加的 GC root. 注意,在并发标记过程中,老年代中被确定为垃圾的对象会被忽略, 即使有外部引用指向他们: 因为在这种情况下引用者也是垃圾。</p><p><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://s3.uuu.ovh/imgs/2022/05/09/c87a52f00aaf8085.png" alt="img_13.png"></p><p>接下来的行为,和其他垃圾收集器一样: 多个GC线程并行地找出哪些是存活对象,确定哪些是垃圾:</p><p><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://s3.uuu.ovh/imgs/2022/05/09/fdb3811b8780c6ce.png" alt="img_14.png"></p><p>最后, 存活对象被转移到存活区(survivor regions), 在必要时会创建新的小堆区。现在,空的小堆区被释放, 可用于存放新的对象了。</p><p><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://s3.uuu.ovh/imgs/2022/05/09/200ca937bd87bd91.png" alt="img_15.png"></p><p>为了维护 remembered set, 在程序运行的过程中, 只要写入某个字段,就会产生一个 Post-Write 屏障。如果生成的引用是跨区域的(cross-region),即从一个区指向另一个区, 就会在目标区的Remembered Set中,出现一个对应的条目。为了减少 Write Barrier 造成的开销, 将卡片放入Remembered Set 的过程是异步的, 而且经过了很多的优化. 总体上是这样: Write Barrier 把脏卡信息存放到本地缓冲区(local buffer), 有专门的GC线程负责收集, 并将相关信息传给被引用区的 remembered set。</p><p>混合模式下的日志, 和纯年轻代模式相比, 可以发现一些有趣的地方:</p><blockquote><p>[<code>[Update RS (ms)</code>: Min: 0.7, Avg: 0.8, Max: 0.9, Diff: 0.2, Sum: 6.1]</p><p><code>[Processed Buffers</code>: Min: 0, Avg: 2.2, Max: 5, Diff: 5, Sum: 18]</p><p><code>[Scan RS (ms)</code>: Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 0.8]</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[Clear CT: 0.2 ms]</span><br><span class="line">[Redirty Cards: 0.1 ms]</span><br></pre></td></tr></table></figure><p>1、 <code>[Update RS (ms)</code> – 因为 Remembered Sets 是并发处理的,必须确保在实际的垃圾收集之前, 缓冲区中的 card 得到处理。如果card数量很多, 则GC并发线程的负载可能就会很高。可能的原因是, 修改的字段过多, 或者CPU资源受限。</p><p>2、 <code>[Processed Buffers</code> – 每个 worker 线程处理了多少个本地缓冲区(local buffer)。</p><p>3、 <code>[Scan RS (ms)</code> – 用了多长时间扫描来自RSet的引用。</p><p>4、 <code>[Clear CT: 0.2 ms]</code> – 清理 card table 中 cards 的时间。清理工作只是简单地删除“脏”状态, 此状态用来标识一个字段是否被更新的, 供Remembered Sets使用。</p><p>5、 <code>[Redirty Cards: 0.1 ms]</code> – 将 card table 中适当的位置标记为 dirty 所花费的时间。”适当的位置”是由GC本身执行的堆内存改变所决定的, 例如引用排队等。</p></blockquote><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>通过本节内容的学习, 你应该对G1垃圾收集器有了一定了解。当然, 为了简洁, 我们省略了很多实现细节， 例如如何处理<a target="_blank" rel="noopener external nofollow noreferrer" href="https://plumbr.eu/handbook/gc-tuning-in-practice#humongous-allocations">巨无霸对象(humongous objects)</a>。 综合来看, G1是HotSpot中最先进的**准产品级(production-ready)**垃圾收集器。重要的是, HotSpot 工程师的主要精力都放在不断改进G1上面, 在新的java版本中,将会带来新的功能和优化。</p><p>可以看到, G1 解决了 CMS 中的各种疑难问题, 包括暂停时间的可预测性, 并终结了堆内存的碎片化。对单业务延迟非常敏感的系统来说, 如果CPU资源不受限制,那么G1可以说是 HotSpot 中最好的选择, 特别是在最新版本的Java虚拟机中。当然,这种降低延迟的优化也不是没有代价的: 由于额外的写屏障(write barriers)和更积极的守护线程, G1的开销会更大。所以, 如果系统属于吞吐量优先型的, 又或者CPU持续占用100%, 而又不在乎单次GC的暂停时间, 那么CMS是更好的选择。</p><blockquote><p>总之: <strong>G1适合大内存,需要低延迟的场景</strong>。</p></blockquote><p>选择正确的GC算法,唯一可行的方式就是去尝试,并找出不对劲的地方, 在下一章我们将给出一般指导原则。</p><p>注意,G1可能会成为Java 9的默认GC: <a target="_blank" rel="noopener external nofollow noreferrer" href="http://openjdk.java.net/jeps/248">http://openjdk.java.net/jeps/248</a></p><h2 id="Shenandoah-的性能"><a href="#Shenandoah-的性能" class="headerlink" title="Shenandoah 的性能"></a>Shenandoah 的性能</h2><blockquote><p><strong>译注</strong>: Shenandoah: 谢南多厄河; 情人渡,水手谣; –&gt; 此款GC暂时没有标准的中文译名; 翻译为大水手垃圾收集器?</p></blockquote><p>我们列出了HotSpot中可用的所有 “准生产级” 算法。还有一种还在实验室中的算法, 称为<strong>超低延迟垃圾收集器(Ultra-Low-Pause-Time Garbage Collector)</strong>. 它的设计目标是管理大型的多核服务器上,超大型的堆内存: 管理 100GB 及以上的堆容量, GC暂停时间小于 10ms。 当然,也是需要和吞吐量进行权衡的: 没有GC暂停的时候,算法的实现对吞吐量的性能损失不能超过10%</p><p>在新算法作为准产品级进行发布之前, 我们不准备去讨论具体的实现细节, 但它也构建在前面所提到的很多算法的基础上, 例如并发标记和增量收集。但其中有很多东西是不同的。它不再将堆内存划分成多个代, 而是只采用单个空间. 没错, Shenandoah 并不是一款分代垃圾收集器。这也就不再需要 card tables 和 remembered sets. 它还使用转发指针(forwarding pointers), 以及Brooks 风格的读屏障(Brooks style read barrier), 以允许对存活对象的并发复制, 从而减少GC暂停的次数和时间。</p></article><div class="post-copyright"><div class="copyright-cc-box"><i class="anzhiyufont anzhiyu-icon-copyright"></i></div><div class="post-copyright__author_box"><a class="post-copyright__author_img" href="/" title="头像"><img class="post-copyright__author_img_back" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://q1.qlogo.cn/g?b=qq&amp;nk=2071916845&amp;s=640" title="头像" alt="头像"><img class="post-copyright__author_img_front" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://q1.qlogo.cn/g?b=qq&amp;nk=2071916845&amp;s=640" title="头像" alt="头像"></a><div class="post-copyright__author_name">云少</div><div class="post-copyright__author_desc">站在巨人的肩膀罢了</div></div><div class="post-copyright__post__info"><a class="post-copyright__original" title="该文章为原创文章，注意版权协议" href="https://it985.github.io/posts/46b81885.html">原创</a><a class="post-copyright-title"><span onclick='rm.copyPageUrl("https://it985.github.io/posts/46b81885.html")'>五、GC 性能优化 – GC 算法(实现篇)</span></a></div><div class="post-tools" id="post-tools"><div class="post-tools-left"><div class="rewardLeftButton"><div class="post-reward" onclick="anzhiyu.addRewardMask()"><div class="reward-button button--animated" title="赞赏作者"><i class="anzhiyufont anzhiyu-icon-hand-heart-fill"></i>打赏作者</div><div class="reward-main"><div class="reward-all"><span class="reward-title">感谢你赐予我前进的力量</span><ul class="reward-group"><li class="reward-item"><a href="/img/wxpay.webp" target="_blank"><img class="post-qr-code-img" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="/img/wxpay.webp" alt="wechat"></a><div class="post-qr-code-desc">wechat</div></li><li class="reward-item"><a href="/img/alipay.webp" target="_blank"><img class="post-qr-code-img" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="/img/alipay.webp" alt="alipay"></a><div class="post-qr-code-desc">alipay</div></li></ul><a class="reward-main-btn" href="/about/#about-reward" target="_blank"><div class="reward-text">赞赏者名单</div><div class="reward-dec">因为你们的支持让我意识到写文章的价值🙏</div></a></div></div></div><div id="quit-box" onclick="anzhiyu.removeRewardMask()" style="display:none"></div></div><div class="shareRight"><div class="share-link mobile"><div class="share-qrcode"><div class="share-button" title="使用手机访问这篇文章"><i class="anzhiyufont anzhiyu-icon-qrcode"></i></div><div class="share-main"><div class="share-main-all"><div id="qrcode" title="https://it985.github.io/posts/46b81885.html"></div><div class="reward-dec">使用手机访问这篇文章</div></div></div></div></div><div class="share-link weibo"><a class="share-button" target="_blank" href="https://service.weibo.com/share/share.php?title=五、GC 性能优化 – GC 算法(实现篇)&amp;url=https://it985.github.io/posts/46b81885.html&amp;pic=https://cn.bing.com/th?id=OHR.NewtonPumpkins_EN-US4897949591_UHD.jpg" rel="external nofollow noreferrer noopener"><i class="anzhiyufont anzhiyu-icon-weibo"></i></a></div><div class="share-link copyurl"><div class="share-button" id="post-share-url" title="复制链接" onclick="rm.copyPageUrl()"><i class="anzhiyufont anzhiyu-icon-link"></i></div></div></div></div></div><div class="post-copyright__notice"><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" rel="external nofollow noreferrer" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://it985.github.io" target="_blank">云少IT</a>！</span></div></div><div class="post-tools-right"><div class="tag_share"><div class="post-meta__box"><div class="post-meta__box__tag-list"><a class="post-meta__box__tags" href="/tags/JVM/"><span class="tags-punctuation"><i class="anzhiyufont anzhiyu-icon-tag"></i></span>JVM<span class="tagsPageCount">8</span></a></div></div><div class="post_share"><div class="social-share" data-image="https://img02.anheyu.com/adminuploads/1/2022/09/05/6315e146a8bbd.webp" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.cbd.int/butterfly-extsrc@1.1.3/sharejs/dist/css/share.min.css" media="print" onload='this.media="all"'><script src="https://cdn.cbd.int/butterfly-extsrc@1.1.3/sharejs/dist/js/social-share.min.js" defer></script></div></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/posts/a63b36e1.html"><img class="prev-cover" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://cn.bing.com/th?id=OHR.RedFoxBlackForest_EN-US4823848176_UHD.jpg" onerror='onerror=null,src="/img/404.jpg"' alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">四、GC 性能优化 – GC 算法(基础篇)</div></div></a></div><div class="next-post pull-right"><a href="/posts/1cbf2ab0.html"><img class="next-cover" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://cn.bing.com/th?id=OHR.MistyForest_EN-US5261676101_UHD.jpg" onerror='onerror=null,src="/img/404.jpg"' alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">六、GC 性能优化 – GC 调优(基础篇)</div></div></a></div></nav><div class="relatedPosts"><div class="headline"><i class="anzhiyufont anzhiyu-icon-thumbs-up fa-fw" style="font-size:1.5rem;margin-right:4px"></i><span>喜欢这篇文章的人也看了</span></div><div class="relatedPosts-list"><div><a href="/posts/727d80c8.html" title="八、GC 性能优化 – GC 调优(实战篇)"><img class="cover" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://cn.bing.com/th?id=OHR.MarigoldsLosMuertos_EN-US5597948262_UHD.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="anzhiyufont anzhiyu-icon-calendar-days fa-fw"></i> 2019-01-31</div><div class="title">八、GC 性能优化 – GC 调优(实战篇)</div></div></a></div><div><a href="/posts/c219fcd0.html" title="七、GC 性能优化 – GC 调优(工具篇)"><img class="cover" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://cn.bing.com/th?id=OHR.KindredSpirits_EN-US5529252474_UHD.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="anzhiyufont anzhiyu-icon-calendar-days fa-fw"></i> 2019-01-30</div><div class="title">七、GC 性能优化 – GC 调优(工具篇)</div></div></a></div><div><a href="/posts/1cbf2ab0.html" title="六、GC 性能优化 – GC 调优(基础篇)"><img class="cover" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://cn.bing.com/th?id=OHR.MistyForest_EN-US5261676101_UHD.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="anzhiyufont anzhiyu-icon-calendar-days fa-fw"></i> 2019-01-29</div><div class="title">六、GC 性能优化 – GC 调优(基础篇)</div></div></a></div><div><a href="/posts/a63b36e1.html" title="四、GC 性能优化 – GC 算法(基础篇)"><img class="cover" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://cn.bing.com/th?id=OHR.RedFoxBlackForest_EN-US4823848176_UHD.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="anzhiyufont anzhiyu-icon-calendar-days fa-fw"></i> 2019-01-27</div><div class="title">四、GC 性能优化 – GC 算法(基础篇)</div></div></a></div><div><a href="/posts/2711940d0c.html" title="三、GC 性能优化 – Java中的垃圾收集"><img class="cover" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://cn.bing.com/th?id=OHR.Bavljenac_EN-US8692148480_UHD.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="anzhiyufont anzhiyu-icon-calendar-days fa-fw"></i> 2019-01-26</div><div class="title">三、GC 性能优化 – Java中的垃圾收集</div></div></a></div><div><a href="/posts/fb861b08.html" title="二、GC 性能优化 – 垃圾收集简介"><img class="cover" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://cn.bing.com/th?id=OHR.Neowise_EN-US4428390515_UHD.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="anzhiyufont anzhiyu-icon-calendar-days fa-fw"></i> 2019-01-25</div><div class="title">二、GC 性能优化 – 垃圾收集简介</div></div></a></div></div></div><hr><div id="post-comment"><div class="comment-head"><div class="comment-headline"><i class="anzhiyufont anzhiyu-icon-comments"></i><span> 评论</span></div><div class="comment-randomInfo"><a onclick="anzhiyu.addRandomCommentInfo()" href="javascript:void(0)" rel="external nofollow noreferrer">匿名评论</a><a href="/privacy" style="margin-left:4px">隐私政策</a></div><div class="comment-switch"><span class="first-comment">Twikoo</span><span id="switch-btn"></span><span class="second-comment">Artalk</span></div><div class="comment-tips" id="comment-tips"><span>✅ 你无需删除空行，直接评论以获取最佳展示效果</span></div></div><div class="comment-wrap"><div><div id="twikoo-wrap"></div></div><div><div id="artalk-wrap"></div></div></div></div><div class="comment-barrage"></div></div><div class="aside-content" id="aside-content"><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="anzhiyufont anzhiyu-icon-bars"></i><span>文章目录</span><span class="toc-percentage"></span></div><div class="toc-content is-expand"><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#%E4%BA%94%E3%80%81GC-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-%E2%80%93-GC-%E7%AE%97%E6%B3%95-%E5%AE%9E%E7%8E%B0%E7%AF%87"><span class="toc-number">1.</span> <span class="toc-text">五、GC 性能优化 – GC 算法(实现篇)</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#Serial-GC-%E4%B8%B2%E8%A1%8CGC"><span class="toc-number">1.1.</span> <span class="toc-text">Serial GC(串行GC)</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Minor-GC-%E5%B0%8F%E5%9E%8BGC"><span class="toc-number">1.1.1.</span> <span class="toc-text">Minor GC(小型GC)</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Full-GC-%E5%AE%8C%E5%85%A8GC"><span class="toc-number">1.1.2.</span> <span class="toc-text">Full GC(完全GC)</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Parallel-GC-%E5%B9%B6%E8%A1%8CGC"><span class="toc-number">1.2.</span> <span class="toc-text">Parallel GC(并行GC)</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Minor-GC-%E5%B0%8F%E5%9E%8BGC-1"><span class="toc-number">1.2.1.</span> <span class="toc-text">Minor GC(小型GC)</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Full-GC-%E5%AE%8C%E5%85%A8GC-1"><span class="toc-number">1.2.2.</span> <span class="toc-text">Full GC(完全GC)</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Concurrent-Mark-and-Sweep-%E5%B9%B6%E5%8F%91%E6%A0%87%E8%AE%B0-%E6%B8%85%E9%99%A4"><span class="toc-number">1.3.</span> <span class="toc-text">Concurrent Mark and Sweep(并发标记-清除)</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Minor-GC-%E5%B0%8F%E5%9E%8BGC-2"><span class="toc-number">1.3.1.</span> <span class="toc-text">Minor GC(小型GC)</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Full-GC-%E5%AE%8C%E5%85%A8GC-2"><span class="toc-number">1.3.2.</span> <span class="toc-text">Full GC(完全GC)</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#G1-%E2%80%93-Garbage-First-%E5%9E%83%E5%9C%BE%E4%BC%98%E5%85%88%E7%AE%97%E6%B3%95"><span class="toc-number">1.4.</span> <span class="toc-text">G1 – Garbage First(垃圾优先算法)</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Evacuation-Pause-Fully-Young-%E8%BD%AC%E7%A7%BB%E6%9A%82%E5%81%9C-%E7%BA%AF%E5%B9%B4%E8%BD%BB%E4%BB%A3%E6%A8%A1%E5%BC%8F"><span class="toc-number">1.4.1.</span> <span class="toc-text">Evacuation Pause: Fully Young(转移暂停:纯年轻代模式)</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Concurrent-Marking-%E5%B9%B6%E5%8F%91%E6%A0%87%E8%AE%B0"><span class="toc-number">1.4.2.</span> <span class="toc-text">Concurrent Marking(并发标记)</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Evacuation-Pause-Mixed-%E8%BD%AC%E7%A7%BB%E6%9A%82%E5%81%9C-%E6%B7%B7%E5%90%88%E6%A8%A1%E5%BC%8F"><span class="toc-number">1.4.3.</span> <span class="toc-text">Evacuation Pause: Mixed (转移暂停: 混合模式)</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%80%BB%E7%BB%93"><span class="toc-number">1.4.4.</span> <span class="toc-text">总结</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Shenandoah-%E7%9A%84%E6%80%A7%E8%83%BD"><span class="toc-number">1.5.</span> <span class="toc-text">Shenandoah 的性能</span></a></li></ol></li></ol></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div id="footer_deal"><a class="deal_link" href="mailto:2071916845@qq.com" rel="external nofollow noreferrer" title="email"><i class="anzhiyufont anzhiyu-icon-envelope"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://weibo.com/" title="微博"><i class="anzhiyufont anzhiyu-icon-weibo"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.facebook.com/" title="facebook"><i class="anzhiyufont anzhiyu-icon-facebook1"></i></a><a class="deal_link" href="/atom.xml" title="RSS"><i class="anzhiyufont anzhiyu-icon-rss"></i></a><img class="footer_mini_logo" title="返回顶部" alt="返回顶部" onclick="anzhiyu.scrollToDest(0,500)" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://q1.qlogo.cn/g?b=qq&amp;nk=2071916845&amp;s=640" size="50px"><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://github.com/it985" title="Github"><i class="anzhiyufont anzhiyu-icon-github"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://space.bilibili.com/300767383" title="Bilibili"><i class="anzhiyufont anzhiyu-icon-bilibili"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://v.douyin.com/" title="抖音"><i class="anzhiyufont anzhiyu-icon-tiktok"></i></a><a class="deal_link" href="/copyright" title="CC"><i class="anzhiyufont anzhiyu-icon-copyright-line"></i></a></div><div id="anzhiyu-footer"><div class="footer-group"><div class="footer-title">服务</div><div class="footer-links"><a class="footer-item" title="51la统计" target="_blank" rel="noopener external nofollow noreferrer" href="https://v6.51.la/">51la统计</a><a class="footer-item" title="十年之约" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.foreverblog.cn/">十年之约</a><a class="footer-item" title="开往" target="_blank" rel="noopener external nofollow noreferrer" href="https://github.com/travellings-link/travellings">开往</a></div></div><div class="footer-group"><div class="footer-title">导航</div><div class="footer-links"><a class="footer-item" title="即刻短文" href="/essay/">即刻短文</a><a class="footer-item" title="友链文章" href="/fcircle/">友链文章</a><a class="footer-item" title="留言板" href="/comments/">留言板</a></div></div><div class="footer-group"><div class="footer-title">协议</div><div class="footer-links"><a class="footer-item" title="免责声明" href="/disclaimer/">免责声明</a><a class="footer-item" title="隐私协议" href="/privacy/">隐私协议</a><a class="footer-item" title="Cookies" href="/cookies/">Cookies</a><a class="footer-item" title="版权协议" href="/copyright/">版权协议</a></div></div><div class="footer-group"><div class="footer-title">娱乐</div><div class="footer-links"><a class="footer-item" title="小空调" href="/air-conditioner/">小空调</a><a class="footer-item" title="围住小猫" href="/catch-cat/">围住小猫</a><a class="footer-item" title="免费图床" href="/huluxiapicture/">免费图床</a></div></div><div class="footer-group"><div class="footer-title">推荐分类</div><div class="footer-links"><a class="footer-item" title="Java" href="/categories/Java/">Java</a><a class="footer-item" title="面试" href="/categories/%E9%9D%A2%E8%AF%95/">面试</a></div></div><div class="footer-group"><div class="footer-title-group"><div class="footer-title">友链</div><a class="random-friends-btn" id="footer-random-friends-btn" href="javascript:addFriendLinksInFooter();" rel="external nofollow noreferrer" title="换一批友情链接"><i class="anzhiyufont anzhiyu-icon-arrow-rotate-right"></i></a></div><div class="footer-links" id="friend-links-in-footer"></div></div></div><div id="workboard"><img class="workSituationImg boardsign" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.0.4/img/badge/安知鱼-上班摸鱼中.svg" alt="距离月入25k也就还差一个大佬带我~" title="距离月入25k也就还差一个大佬带我~"><div id="runtimeTextTip"></div></div><div class="wordcount"></div><span>云少已经写了 996.7k 字，</span><span>好像写完一本我国著名的 四大名著 了！！！</span><p id="ghbdages"><a class="github-badge" target="_blank" href="https://hexo.io/" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="博客框架为Hexo_v5.4.0" title="博客框架为Hexo_v5.4.0"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.1.5/img/badge/Frame-Hexo.svg" alt="博客框架为Hexo_v5.4.0"></a><a class="github-badge" target="_blank" href="https://www.upyun.com/?utm_source=lianmeng&amp;utm_medium=referral" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="本站使用又拍云为静态资源提供CDN加速" title="本站使用又拍云为静态资源提供CDN加速"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://img.shields.io/badge/CDN-%E5%8F%88%E6%8B%8D%E4%BA%91-orange%3Fstyle%3Dflat%26logo%3D%E5%8F%88%E6%8B%8D%E4%BA%91" alt="本站使用又拍云为静态资源提供CDN加速"></a><a class="github-badge" target="_blank" href="https://github.com/" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="本站项目由Github托管" title="本站项目由Github托管"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.1.5/img/badge/Source-Github.svg" alt="本站项目由Github托管"></a><a class="github-badge" target="_blank" href="http://creativecommons.org/licenses/by-nc-sa/4.0/" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="本站采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可" title="本站采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.2.0/img/badge/Copyright-BY-NC-SA.svg" alt="本站采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可"></a><a class="github-badge" target="_blank" href="https://icp.gov.moe/?keyword=20221607" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="萌ICP备20221607号" title="萌ICP备20221607号"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://img.shields.io/badge/%E8%90%8CICP%E5%A4%87-20221607-fe1384?style-flat&amp;logo=" alt="萌ICP备20221607号"></a></p></div><div id="footer-bar"><div class="footer-bar-links"><div class="footer-bar-left"><div id="footer-bar-tips"><div class="copyright">&copy;2018 - 2023 By <a class="footer-bar-link" href="/" title="云少" target="_blank">云少</a></div></div><div id="footer-type-tips"></div><div class="js-pjax"><script>function subtitleType(){fetch("https://v1.hitokoto.cn").then(t=>t.json()).then(t=>{var e="出自 "+t.from,p=[];p.unshift(t.hitokoto,e),window.typed=new Typed("#footer-type-tips",{strings:p,startDelay:300,typeSpeed:150,loop:!0,backSpeed:50})})}"function"==typeof Typed?subtitleType():getScript("https://cdn.cbd.int/typed.js@2.0.15/dist/typed.umd.js").then(subtitleType)</script></div></div><div class="footer-bar-right"><a class="footer-bar-link" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top" title="云少">云少</a><a class="footer-bar-link" target="_blank" rel="noopener external nofollow noreferrer" href="https://beian.miit.gov.cn/" title="鄂ICP备2021021095号-2">鄂ICP备2021021095号-2</a><a class="footer-bar-link cc" href="/copyright" title="cc协议"><i class="anzhiyufont anzhiyu-icon-copyright-line"></i><i class="anzhiyufont anzhiyu-icon-creative-commons-by-line"></i><i class="anzhiyufont anzhiyu-icon-creative-commons-nc-line"></i><i class="anzhiyufont anzhiyu-icon-creative-commons-nd-line"></i></a></div></div></div></footer></div><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="sidebar-site-data site-data is-center"><a href="/archives/" title="archive"><div class="headline">文章</div><div class="length-num">861</div></a><a href="/tags/" title="tag"><div class="headline">标签</div><div class="length-num">35</div></a><a href="/categories/" title="category"><div class="headline">分类</div><div class="length-num">6</div></a></div><span class="sidebar-menu-item-title">功能</span><div class="sidebar-menu-item"><a class="darkmode_switchbutton menu-child" href="javascript:void(0);" rel="external nofollow noreferrer" title="显示模式"><i class="anzhiyufont anzhiyu-icon-circle-half-stroke"></i><span>显示模式</span></a></div><div class="back-menu-list-groups"><div class="back-menu-list-group"><div class="back-menu-list-title">网页</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top/" title="博客"><img class="back-menu-item-icon" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="/img/favicon.ico" alt="博客"><span class="back-menu-item-text">博客</span></a></div></div><div class="back-menu-list-group"><div class="back-menu-list-title">项目</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top" title="图床"><img class="back-menu-item-icon" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://www.tryrun.top/favicon.ico" alt="图床"><span class="back-menu-item-text">图床</span></a></div></div></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>望四方</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/archives/"><i class="anzhiyufont anzhiyu-icon-box-archive faa-tada" style="font-size:.9em"></i><span> 归名档</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/categories/"><i class="anzhiyufont anzhiyu-icon-shapes faa-tada" style="font-size:.9em"></i><span> 归四类</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/tags/"><i class="anzhiyufont anzhiyu-icon-tags faa-tada" style="font-size:.9em"></i><span> 书中签</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/charts/"><i class="fa-fw fas fa-chart-bar faa-tada"></i><span> 统计</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>友链</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/link/"><i class="anzhiyufont anzhiyu-icon-link faa-tada" style="font-size:.9em"></i><span> 四方好友</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/fcircle/"><i class="anzhiyufont anzhiyu-icon-artstation faa-tada" style="font-size:.9em"></i><span> 朋友圈</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/comments/"><i class="anzhiyufont anzhiyu-icon-envelope faa-tada" style="font-size:.9em"></i><span> 留言板</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>我的</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/bangumis/"><i class="anzhiyufont anzhiyu-icon-bilibili faa-tada" style="font-size:.9em"></i><span> 追番页</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/album/"><i class="anzhiyufont anzhiyu-icon-images faa-tada" style="font-size:.9em"></i><span> 刹那间</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/equipment/"><i class="fas fa-heart faa-tada"></i><span> 我的装备</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/collect/"><i class="fas fa-camcorder faa-tada"></i><span> 观影阁</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>关于</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/about/"><i class="anzhiyufont anzhiyu-icon-paper-plane faa-tada" style="font-size:.9em"></i><span> 关于本人</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/essay/"><i class="anzhiyufont anzhiyu-icon-lightbulb faa-tada" style="font-size:.9em"></i><span> 闲言碎语</span></a></li><li><a class="site-page child faa-parent animated-hover" href="javascript:toRandomPost()" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-shoe-prints1 faa-tada" style="font-size:.9em"></i><span> 随便逛逛</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/disclaimer/"><i class="fas fa-heart faa-tada"></i><span> 免责声明</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/love/"><i class="anzhiyufont anzhiyu-icon-heartbeat faa-tada" style="font-size:.9em"></i><span> 恋爱小屋</span></a></li></ul></div></div><span class="sidebar-menu-item-title">标签</span><div class="card-tags"><div class="item-headline"></div><div class="card-tag-cloud"><a href="/tags/API/" style="font-size:.88rem;color:#758692">API<sup>43</sup></a><a href="/tags/Base64/" style="font-size:.88rem;color:#2584bf">Base64<sup>1</sup></a><a href="/tags/Collectors/" style="font-size:.88rem;color:#bfad3e">Collectors<sup>3</sup></a><a href="/tags/Date/" style="font-size:.88rem;color:#b9b609">Date<sup>3</sup></a><a href="/tags/Executor/" style="font-size:.88rem;color:#14c83c">Executor<sup>9</sup></a><a href="/tags/Guava/" style="font-size:.88rem;color:#0a1878">Guava<sup>1</sup></a><a href="/tags/JVM/" style="font-size:.88rem;color:#625421">JVM<sup>8</sup></a><a href="/tags/Java8/" style="font-size:.88rem;color:#aa71bf">Java8<sup>21</sup></a><a href="/tags/Java9/" style="font-size:.88rem;color:#ad8774">Java9<sup>21</sup></a><a href="/tags/Java%E5%B9%B6%E5%8F%91/" style="font-size:.88rem;color:#19704c">Java并发<sup>20</sup></a><a href="/tags/Lambda/" style="font-size:.88rem;color:#7e5c78">Lambda<sup>4</sup></a><a href="/tags/Lock/" style="font-size:.88rem;color:#700769">Lock<sup>1</sup></a><a href="/tags/Maven/" style="font-size:.88rem;color:#2b8671">Maven<sup>1</sup></a><a href="/tags/Memcached/" style="font-size:.88rem;color:#1b0694">Memcached<sup>23</sup></a><a href="/tags/Mongodb/" style="font-size:.88rem;color:#36202d">Mongodb<sup>49</sup></a><a href="/tags/Queue/" style="font-size:.88rem;color:#643f76">Queue<sup>1</sup></a><a href="/tags/Redis/" style="font-size:.88rem;color:#1e4c38">Redis<sup>27</sup></a><a href="/tags/Stream/" style="font-size:.88rem;color:#a5a097">Stream<sup>4</sup></a><a href="/tags/Thread/" style="font-size:.88rem;color:#05682d">Thread<sup>7</sup></a><a href="/tags/Thread-pool/" style="font-size:.88rem;color:#38ad2a">Thread pool<sup>7</sup></a><a href="/tags/forkJoinPool/" style="font-size:.88rem;color:#827a6b">forkJoinPool<sup>2</sup></a><a href="/tags/stream/" style="font-size:.88rem;color:#4a3ac7">stream<sup>1</sup></a><a href="/tags/%E4%B8%93%E6%A0%8F/" style="font-size:.88rem;color:#b89c14">专栏<sup>35</sup></a><a href="/tags/%E4%BA%8B%E5%8A%A1/" style="font-size:.88rem;color:#922401">事务<sup>1</sup></a><a href="/tags/%E4%BC%98%E5%8C%96/" style="font-size:.88rem;color:#437d09">优化<sup>1</sup></a><a href="/tags/%E5%91%BD%E4%BB%A4/" style="font-size:.88rem;color:#705b06">命令<sup>57</sup></a><a href="/tags/%E5%AE%89%E8%A3%85/" style="font-size:.88rem;color:#06a544">安装<sup>6</sup></a><a href="/tags/%E5%B7%A5%E5%85%B7/" style="font-size:.88rem;color:#c86c4f">工具<sup>2</sup></a><a href="/tags/%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/" style="font-size:.88rem;color:#560b2c">数据类型<sup>8</sup></a><a href="/tags/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/" style="font-size:.88rem;color:#4e0a32">生命周期<sup>1</sup></a><a href="/tags/%E7%AE%80%E4%BB%8B/" style="font-size:.88rem;color:#230f8e">简介<sup>7</sup></a><a href="/tags/%E7%AE%97%E6%B3%95/" style="font-size:.88rem;color:#c0771e">算法<sup>10</sup></a><a href="/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" style="font-size:.88rem;color:#b40caa">设计模式<sup>38</sup></a><a href="/tags/%E9%85%8D%E7%BD%AE/" style="font-size:.88rem;color:#67389a">配置<sup>2</sup></a><a href="/tags/%E9%9D%A2%E8%AF%95/" style="font-size:.88rem;color:#57425c">面试<sup>11</sup></a></div></div><hr></div></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="anzhiyufont anzhiyu-icon-book-open"></i></button><button id="translateLink" type="button" title="简繁转换">繁</button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="anzhiyufont anzhiyu-icon-circle-half-stroke"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="anzhiyufont anzhiyu-icon-arrows-left-right"></i></button></div><div id="rightside-config-show"><button id="rightside-config" type="button" title="设置"><i class="anzhiyufont anzhiyu-icon-gear"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="anzhiyufont anzhiyu-icon-list-ul"></i></button><a id="to_comment" href="#post-comment" title="直达评论"><i class="anzhiyufont anzhiyu-icon-comments"></i></a><button type="button" title="切换背景" onclick="toggleWinbox()"><i class="fas fa-display"></i></button><button id="go-up" type="button" title="回到顶部"><i class="anzhiyufont anzhiyu-icon-arrow-up"></i></button></div></div><div id="algolia-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">搜索</span><button class="search-close-button"><i class="anzhiyufont anzhiyu-icon-xmark"></i></button></nav><div class="search-wrap"><div id="algolia-search-input"></div><hr><div id="algolia-search-results"><div id="algolia-hits"></div><div id="algolia-pagination"></div><div id="algolia-info"><div class="algolia-stats"></div><div class="algolia-poweredBy"></div></div></div></div></div><div id="search-mask"></div></div><div id="rightMenu"><div class="rightMenu-group rightMenu-small"><div class="rightMenu-item" id="menu-backward"><i class="anzhiyufont anzhiyu-icon-arrow-left"></i></div><div class="rightMenu-item" id="menu-forward"><i class="anzhiyufont anzhiyu-icon-arrow-right"></i></div><div class="rightMenu-item" id="menu-refresh"><i class="anzhiyufont anzhiyu-icon-arrow-rotate-right" style="font-size:1rem"></i></div><div class="rightMenu-item" id="menu-top"><i class="anzhiyufont anzhiyu-icon-arrow-up"></i></div></div><div class="rightMenu-group rightMenu-line rightMenuPlugin"><div class="rightMenu-item" id="menu-copytext"><i class="anzhiyufont anzhiyu-icon-copy"></i><span>复制选中文本</span></div><div class="rightMenu-item" id="menu-pastetext"><i class="anzhiyufont anzhiyu-icon-paste"></i><span>粘贴文本</span></div><a class="rightMenu-item" id="menu-commenttext"><i class="anzhiyufont anzhiyu-icon-comment-medical"></i><span>引用到评论</span></a><div class="rightMenu-item" id="menu-newwindow"><i class="anzhiyufont anzhiyu-icon-window-restore"></i><span>新窗口打开</span></div><div class="rightMenu-item" id="menu-copylink"><i class="anzhiyufont anzhiyu-icon-link"></i><span>复制链接地址</span></div><div class="rightMenu-item" id="menu-copyimg"><i class="anzhiyufont anzhiyu-icon-images"></i><span>复制此图片</span></div><div class="rightMenu-item" id="menu-downloadimg"><i class="anzhiyufont anzhiyu-icon-download"></i><span>下载此图片</span></div><div class="rightMenu-item" id="menu-newwindowimg"><i class="anzhiyufont anzhiyu-icon-window-restore"></i><span>新窗口打开图片</span></div><div class="rightMenu-item" id="menu-search"><i class="anzhiyufont anzhiyu-icon-magnifying-glass"></i><span>站内搜索</span></div><div class="rightMenu-item" id="menu-searchBaidu"><i class="anzhiyufont anzhiyu-icon-magnifying-glass"></i><span>百度搜索</span></div><div class="rightMenu-item" id="menu-music-toggle"><i class="anzhiyufont anzhiyu-icon-play"></i><span>播放音乐</span></div><div class="rightMenu-item" id="menu-music-back"><i class="anzhiyufont anzhiyu-icon-backward"></i><span>切换到上一首</span></div><div class="rightMenu-item" id="menu-music-forward"><i class="anzhiyufont anzhiyu-icon-forward"></i><span>切换到下一首</span></div><div class="rightMenu-item" id="menu-music-playlist" onclick="window.open(&quot;https://y.qq.com/n/ryqq/playlist/8802438608&quot;, &quot;_blank&quot;);" style="display:none"><i class="anzhiyufont anzhiyu-icon-radio"></i><span>查看所有歌曲</span></div><div class="rightMenu-item" id="menu-music-copyMusicName"><i class="anzhiyufont anzhiyu-icon-copy"></i><span>复制歌名</span></div></div><div class="rightMenu-group rightMenu-line rightMenuOther"><a class="rightMenu-item menu-link" id="menu-randomPost"><i class="anzhiyufont anzhiyu-icon-shuffle"></i><span>随便逛逛</span></a><a class="rightMenu-item menu-link" href="/categories/"><i class="anzhiyufont anzhiyu-icon-cube"></i><span>博客分类</span></a><a class="rightMenu-item menu-link" href="/tags/"><i class="anzhiyufont anzhiyu-icon-tags"></i><span>文章标签</span></a></div><div class="rightMenu-group rightMenu-line rightMenuOther"><a class="rightMenu-item" id="menu-copy" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-copy"></i><span>复制地址</span></a><a class="rightMenu-item" id="menu-commentBarrage" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-message"></i><span class="menu-commentBarrage-text">关闭热评</span></a><a class="rightMenu-item" id="menu-darkmode" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-circle-half-stroke"></i><span class="menu-darkmode-text">深色模式</span></a><a class="rightMenu-item" id="menu-translate" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-language"></i><span>轉為繁體</span></a></div></div><div id="rightmenu-mask"></div><div id="he-plugin-simple"></div><script>var WIDGET={CONFIG:{modules:"0124",background:"2",tmpColor:"FFFFFF",tmpSize:"16",cityColor:"FFFFFF",citySize:"16",aqiColor:"E8D87B",aqiSize:"16",weatherIconSize:"24",alertIconSize:"18",padding:"10px 10px 10px 10px",shadow:"0",language:"auto",borderRadius:"20",fixed:"true",vertical:"top",horizontal:"left",left:"20",top:"7.1",key:"df245676fb434a0691ead1c63341cd94"}}</script><link rel="stylesheet" href="https://widget.qweather.net/simple/static/css/he-simple.css?v=1.4.0"><script src="https://widget.qweather.net/simple/static/js/he-simple.js?v=1.4.0"></script><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="/js/tw_cn.js"></script><script src="https://cdn.cbd.int/@fancyapps/ui@5.0.20/dist/fancybox/fancybox.umd.js"></script><script src="https://cdn.cbd.int/instant.page@5.2.0/instantpage.js" type="module"></script><script src="https://cdn.cbd.int/vanilla-lazyload@17.8.4/dist/lazyload.iife.min.js"></script><script src="https://cdn.cbd.int/node-snackbar@0.1.16/dist/snackbar.min.js"></script><script>function panguFn(){"object"==typeof pangu?pangu.autoSpacingPage():getScript("https://cdn.cbd.int/pangu@4.0.7/dist/browser/pangu.min.js").then(()=>{pangu.autoSpacingPage()})}function panguInit(){panguFn()}document.addEventListener("DOMContentLoaded",panguInit)</script><script>var meting_api="https://api.injahow.cn/meting/?server=:server&type=:type&id=:id&auth=:auth&r=:r"</script><canvas id="universe"></canvas><script async src="https://npm.elemecdn.com/anzhiyu-theme-static@1.0.0/dark/dark.js"></script><script>var HoldLog=console.log;console.log=function(){};let now1=new Date;queueMicrotask(()=>{function o(){HoldLog.apply(console,arguments)}var c=new Date("09/01/2018 00:00:00"),c=(now1.setTime(now1.getTime()+250),(now1-c)/1e3/60/60/24),c=["欢迎使用安知鱼!","生活明朗, 万物可爱",`
        
       █████╗ ███╗   ██╗███████╗██╗  ██╗██╗██╗   ██╗██╗   ██╗
      ██╔══██╗████╗  ██║╚══███╔╝██║  ██║██║╚██╗ ██╔╝██║   ██║
      ███████║██╔██╗ ██║  ███╔╝ ███████║██║ ╚████╔╝ ██║   ██║
      ██╔══██║██║╚██╗██║ ███╔╝  ██╔══██║██║  ╚██╔╝  ██║   ██║
      ██║  ██║██║ ╚████║███████╗██║  ██║██║   ██║   ╚██████╔╝
      ╚═╝  ╚═╝╚═╝  ╚═══╝╚══════╝╚═╝  ╚═╝╚═╝   ╚═╝    ╚═════╝
        
        `,"已上线",Math.floor(c),"天","©2018 By 安知鱼 V1.6.6"],e=["NCC2-036","调用前置摄像头拍照成功，识别为【小笨蛋】.","Photo captured: ","🤪"];setTimeout(o.bind(console,`
%c${c[0]} %c ${c[1]} %c ${c[2]} %c${c[3]}%c ${c[4]}%c ${c[5]}

%c ${c[6]}
`,"color:#425AEF","","color:#425AEF","color:#425AEF","","color:#425AEF","")),setTimeout(o.bind(console,`%c ${e[0]} %c ${e[1]} %c 
${e[2]} %c
${e[3]}
`,"color:white; background-color:#4fd953","","",'background:url("https://npm.elemecdn.com/anzhiyu-blog@1.1.6/img/post/common/tinggge.gif") no-repeat;font-size:450%')),setTimeout(o.bind(console,"%c WELCOME %c 你好，小笨蛋.","color:white; background-color:#4f90d9","")),setTimeout(console.warn.bind(console,"%c ⚡ Powered by 安知鱼 %c 你正在访问 云少 的博客.","color:white; background-color:#f0ad4e","")),setTimeout(o.bind(console,"%c W23-12 %c 你已打开控制台.","color:white; background-color:#4f90d9","")),setTimeout(console.warn.bind(console,"%c S013-782 %c 你现在正处于监控中.","color:white; background-color:#d9534f",""))})</script><script async src="/anzhiyu/random.js"></script><script async>!function(){var n,o,r,a,i,e=new Date("09/01/2018 00:00:00"),l=new Date;setInterval(()=>{var t;if(l=new Date,i=l.getHours(),t=(l-e)/1e3/60/60/24,n=Math.floor(t),t=(l-e)/1e3/60/60-24*n,o=Math.floor(t),1==String(o).length&&(o="0"+o),t=(l-e)/1e3/60-1440*n-60*o,r=Math.floor(t),1==String(r).length&&(r="0"+r),t=(l-e)/1e3-86400*n-3600*o-60*r,a=Math.round(t),1==String(a).length&&(a="0"+a),document.getElementById("footer")){let e="";e=(i<18&&9<=i||((t=document.querySelector("#workboard .workSituationImg")).src="https://npm.elemecdn.com/anzhiyu-blog@2.0.4/img/badge/安知鱼-下班啦.svg",t.title="下班了就该开开心心的玩耍，嘿嘿~",t.alt="下班了就该开开心心的玩耍，嘿嘿~"),`本站居然运行了 ${n} 天<span id='runtime'> ${o} 小时 ${r} 分 ${a} 秒 </span><i class='anzhiyufont anzhiyu-icon-heartbeat' style='color:red'></i>`),document.getElementById("runtimeTextTip")&&(document.getElementById("runtimeTextTip").innerHTML=e)}},1e3)}()</script><script src="https://cdn.cbd.int/algoliasearch@4.18.0/dist/algoliasearch-lite.umd.js"></script><script src="https://cdn.cbd.int/instantsearch.js@4.56.5/dist/instantsearch.production.min.js"></script><script src="/js/search/algolia.js"></script><div class="js-pjax"><script>(()=>{const e=document.querySelectorAll("#article-container .mermaid-wrap");if(0!==e.length){const n=()=>{window.loadMermaid=!0;const a="dark"===document.documentElement.getAttribute("data-theme")?"dark":"default";Array.from(e).forEach((e,t)=>{const n=e.firstElementChild;e="%%{init:{ 'theme':'"+a+"'}}%%\n"+n.textContent;const d=mermaid.render("mermaid-"+t,e);"string"==typeof d?(t=d,n.insertAdjacentHTML("afterend",t)):d.then(({svg:e})=>{n.insertAdjacentHTML("afterend",e)})})};var t=()=>{window.loadMermaid?n():getScript("https://cdn.cbd.int/mermaid@10.2.4/dist/mermaid.min.js").then(n)};anzhiyu.addGlobalFn("themeChange",n,"mermaid"),window.pjax?t():document.addEventListener("DOMContentLoaded",t)}})()</script><script>(()=>{const t=()=>{twikoo.init(Object.assign({el:"#twikoo-wrap",envId:"https://twikoo.tryrun.top/",region:"",onCommentLoaded:()=>{anzhiyu.loadLightbox(document.querySelectorAll("#twikoo .tk-content img:not(.tk-owo-emotion)"))}},null))};var o=()=>{"object"==typeof twikoo?setTimeout(n,0):getScript("https://cdn.cbd.int/twikoo@1.6.18/dist/twikoo.all.min.js").then(n)};const n=()=>{t()};anzhiyu.loadComment(document.getElementById("twikoo-wrap"),o)})()</script><script>(()=>{const t=()=>{window.artalkItem=new Artalk(Object.assign({el:"#artalk-wrap",server:"https://artalk.tryrun.top/",site:"https://blog.tryrun.top/",pageKey:location.pathname,darkMode:"dark"===document.documentElement.getAttribute("data-theme"),countEl:".artalk-count"},null)),"null"!==GLOBAL_CONFIG.lightbox&&window.artalkItem.use(t=>{t.on("list-loaded",()=>{t.getCommentList().forEach(t=>{t=t.getRender().$content;anzhiyu.loadLightbox(t.querySelectorAll("img:not([atk-emoticon])"))})})})};var a=async()=>{"object"==typeof window.artalkItem||(await getCSS("https://cdn.cbd.int/artalk@2.5.5/dist/Artalk.css"),await getScript("https://cdn.cbd.int/artalk@2.5.5/dist/Artalk.js")),t()};anzhiyu.addGlobalFn("themeChange",t=>{var a=document.getElementById("artalk-wrap");a&&a.children.length&&window.artalkItem.setDarkMode("dark"===t)},"artalk"),window.loadOtherComment=a})()</script><input type="hidden" name="page-type" id="page-type" value="post"></div><script>window.addEventListener("load",()=>{const t=e=>e=""!==e&&150<(e=(e=(e=(e=e.replace(/<img.*?src="(.*?)"?[^\>]+>/gi,"[图片]")).replace(/<a[^>]+?href=["']?([^"']+)["']?[^>]*>([^<]+)<\/a>/gi,"[链接]")).replace(/<pre><code>.*?<\/pre>/gi,"[代码]")).replace(/<[^>]+>/g,"")).length?e.substring(0,150)+"...":e,n=t=>{let n="";if(t.length)for(let e=0;e<t.length;e++)n=(n=(n+="<div class='aside-list-item'>")+`<a href='${t[e].url}' class='thumbnail'><img data-lazy-src='${t[e].avatar}' alt='${t[e].nick}'><div class='name'><span>${t[e].nick} </span></div></a>`)+`<div class='content'>
        <a class='comment' href='${t[e].url}' title='${t[e].content}'>${t[e].content}</a>
        <time datetime="${t[e].date}">${anzhiyu.diffDate(t[e].date,!0)}</time></div>
        </div>`;else n+="没有评论";var e=document.querySelector("#card-newest-comments .aside-list");e.innerHTML=n,window.lazyLoadInstance&&window.lazyLoadInstance.update(),window.pjax&&window.pjax.refresh(e)};var e=()=>{var e;document.querySelector("#card-newest-comments .aside-list")&&((e=saveToLocal.get("twikoo-newest-comments"))?n(JSON.parse(e)):(e=()=>{twikoo.getRecentComments({envId:"https://twikoo.tryrun.top/",region:"",pageSize:6,includeReply:!0}).then(function(e){e=e.map(e=>({content:t(e.comment),avatar:e.avatar,nick:e.nick,url:e.url+"#"+e.id,date:new Date(e.created).toISOString()}));saveToLocal.set("twikoo-newest-comments",JSON.stringify(e),10/1440),n(e)}).catch(function(e){document.querySelector("#card-newest-comments .aside-list").textContent="无法获取评论，请确认相关配置是否正确"})},"object"==typeof twikoo?e():getScript("https://cdn.cbd.int/twikoo@1.6.18/dist/twikoo.all.min.js").then(e)))};e(),document.addEventListener("pjax:complete",e)})</script><script async data-pjax src="https://npm.elemecdn.com/anzhiyu-theme-static@1.0.1/bubble/bubble.js"></script><script>var visitorMail="visitor@anheyu.com"</script><script async data-pjax src="https://cdn.cbd.int/anzhiyu-theme-static@1.0.0/waterfall/waterfall.js"></script><script src="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/qrcodejs/1.0.0/qrcode.min.js"></script><script src="/js/anzhiyu/right_click_menu.js"></script><link rel="stylesheet" href="https://cdn.cbd.int/anzhiyu-theme-static@1.1.9/icon/ali_iconfont_css.css"><script async src="/js/1.min.js?1"></script><script async src="/js/bg.js?1"></script><script async src="https://cdn1.tianli0.top/npm/sweetalert2@8.19.0/dist/sweetalert2.all.js"></script><script async src="/js/lunar.min.js?1"></script><script async src="/js/day.min.js?1"></script><script async src="/js/winbox.bundle.min.js?1"></script><script id="click-show-text" src="https://cdn.cbd.int/butterfly-extsrc@1.1.3/dist/click-show-text.min.js" data-mobile="true" data-text="富强,民主,文明,和谐,自由,平等,公正,法治,爱国,敬业,诚信,友善" data-fontsize="15px" data-random="true" async></script><link rel="stylesheet" href="https://cdn.cbd.int/anzhiyu-theme-static@1.0.0/aplayer/APlayer.min.css" media="print" onload='this.media="all"'><script src="https://cdn.cbd.int/anzhiyu-blog-static@1.0.1/js/APlayer.min.js"></script><script src="https://cdn.cbd.int/hexo-anzhiyu-music@1.0.1/assets/js/Meting2.min.js"></script><script src="https://cdn.cbd.int/pjax@0.2.8/pjax.min.js"></script><script>let pjaxSelectors=['meta[property="og:image"]','meta[property="og:title"]','meta[property="og:url"]','meta[property="og:type"]','meta[property="og:site_name"]','meta[property="og:description"]',"head > title","#config-diff","#body-wrap","#rightside-config-hide","#rightside-config-show",".js-pjax"];var pjax=new Pjax({elements:'a:not([target="_blank"])',selectors:pjaxSelectors,cacheBust:!1,analytics:!0,scrollRestoration:!1});document.addEventListener("pjax:send",function(){if(anzhiyu.removeGlobalFnEvent("pjax"),anzhiyu.removeGlobalFnEvent("themeChange"),document.getElementById("rightside").classList.remove("rightside-show"),window.aplayers)for(let e=0;e<window.aplayers.length;e++)window.aplayers[e].options.fixed||window.aplayers[e].destroy();"object"==typeof typed&&typed.destroy();var e=document.body.classList;e.contains("read-mode")&&e.remove("read-mode")}),document.addEventListener("pjax:complete",function(){window.refreshFn(),document.querySelectorAll("script[data-pjax]").forEach(e=>{const t=document.createElement("script");var a=e.text||e.textContent||e.innerHTML||"";Array.from(e.attributes).forEach(e=>t.setAttribute(e.name,e.value)),t.appendChild(document.createTextNode(a)),e.parentNode.replaceChild(t,e)}),GLOBAL_CONFIG.islazyload&&window.lazyLoadInstance.update(),"function"==typeof chatBtnFn&&chatBtnFn(),"function"==typeof panguInit&&panguInit(),"function"==typeof gtag&&gtag("config","G-3VMKW5TZBM",{page_path:window.location.pathname}),"object"==typeof _hmt&&_hmt.push(["_trackPageview",window.location.pathname]),"function"==typeof loadMeting&&document.getElementsByClassName("aplayer").length&&loadMeting(),"object"==typeof Prism&&Prism.highlightAll()}),document.addEventListener("pjax:error",e=>{404===e.request.status&&pjax.loadUrl("/404.html")})</script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><script charset="UTF-8" src="https://cdn.cbd.int/anzhiyu-theme-static@1.1.5/accesskey/accesskey.js"></script></div><div id="popup-window"><div class="popup-window-title">通知</div><div class="popup-window-divider"></div><div class="popup-window-content"><div class="popup-tip">你好呀</div><div class="popup-link"><i class="anzhiyufont anzhiyu-icon-arrow-circle-right"></i></div></div></div></body></html>